''continue working on PID and flight routines. Test Display implementation!!!! 02/24/2011
'''create conditions for pitch and roll orientation, do not use PID loop.
'''use a ranged value, set point pitch of 0, if pitch is inbetween -5 and 5 then OKAY!
{{
COG COUNT
Cog 0 = Main Program Loop
Cog 1 = ESC Motor Driver
Cog 2 = FullDuplexSerial
Cog 3 = F32
Cog 4 = Flight Control_PID_Kalman
Cog 5 =
Cog 6 =
Cog 7 =
Flight_Control(PID, Kalman Filter, Wii Objects)
''''OLD''''''
Possible Solutions to Free Up Cogs
Combine Flight Control/PID into main program loop.
Do not give Propeller to Spinneret Communication it's own cog.
Eliminate the need for 2 cogs for the Floating Point Math.
The above free's up 4 cogs, including the Display Test Values.
Results:
Flight_control is no longer using a cog, I have put a call to that funtion in each of the Decision engine's routine.
I eliminated the 2nd cog for floating math, I am using F32 instead.....
For the moment I took out the propeller to spinneret communication, the display test values is using its cog.
Total Cogs Used : 4
Total Free Cogs : 4
IF no communication and no display test values then total free cogs = 5
}}
CON
_CLKMODE = XTAL1 + PLL16X
_XINFREQ = 5_000_000
CLOCKS_PER_MICROSECOND = 5*16 ' simple xin*pll / 1_000_000
CLOCKS_PER_MILLISECOND = 5000*16 ' simple xin*pll / 1_000
Error_LED = 0
ON = 1
OFF = 0
RXPIN = 11
TXPIN = 12
SCL = 28
SDA = 29
Mtr_A = 0
Mtr_B = 1
Mtr_C = 2
Mtr_D = 3
Auto_Forward = 50 ''FIXED POINT VALUES ONLY
Auto_Reverse = -50
Auto_Left = -50
Auto_Right = 50
Auto_Rotate = 50
CLS = 16, CR = 13, CLREOL = 11, CRSRXY = 2
MP_ONLY = 4 'MotionPlus only
MP_NUN = 5 'MotionPlus w/ Nunchuck
MP_CLASSIC = 7 'MotionPlus w/ Classic Controller
VAR
long Accel_X, Accel_Y
long Gx_F, Gy_F, Gz_F
word Alt,Thrust
word Heading, Bearing, Temp
long SP_Alt, SP_Head
byte Error_Status, User_Control, i
byte Wants_Control, Has_Control
byte Landing, TakingOff, Holding, Rotating, Moving
long RI_IR, LE_IR, FR_IR, RR_IR, TO_IR, BO_IR
long count
long cur_State
byte startbtn
long M_A, M_B, M_C, M_D
'long Received_Commands, RecBuffer[6], SenBuffer[6]
'long stack[200]
OBJ
Ser : "FullDuplexSerialPlus"
FC : "Flight_Control0429"
SCG : "sparecogs"
SIR : "Sharp_IR"
ESC : "ESC_Spin"
Clk : "Clock"
'fString : "FloatString"
'TxRx : "FullDuplexSerialPlus"
PUB MAIN | q
'''''''''''''''''''''''INITIALIZING OBJECT DRIVERS'''''''''''''''''''''''''''''''''''''''
'TxRx.start(11, 12, 0, 115200)
Ser.start(31,30,0,115200)
ESC.Start
FC.Init(SCL, SDA, MP_NUN)
Delay_MS(2000)
'''''''''''''''''''''''INITIALIZE ALL CONDITIONS''''''''''''''''''''''
Error_Status := Wants_Control := Has_Control := User_Control := 0
SP_Alt := Alt := Temp := Thrust := 0
FR_IR := RR_IR := LE_IR := RI_IR := TO_IR := BO_IR := 0
Landing := TakingOff := Rotating := Moving := Holding := 0
Thrust := 0
'''''''''''''''''''''''MAIN PROGRAM CODE''''''''''''''''''''''''''''''''''''''''''''''''''
startbtn := 0
DIRA[14]~~
OUTA[14]~~
DIRA[15]~
Delay_MS(1000)
REPEAT UNTIL startbtn == 1
IF INA[15] == 1
Delay_MS(500)
IF INA[15] == 1
Delay_MS(500)
IF INA[15] == 1
startbtn := 1
TestDisplay
Delay_MS(2000)
'''''''''''''''''''''''INITIALIZE ESC's AND MOTOR's''''''''''''''''''''''
ESC.Servo(Mtr_A,1000)
ESC.Servo(Mtr_B,1000)
ESC.Servo(Mtr_C,1000)
ESC.Servo(Mtr_D,1000)
Delay_MS(10000)
''//////////////////Slow Start Sequence///////////////////////////
Decision_Engine(9)
''//////////////////Take-Off Sequence///////////////////////////
'Decision_Engine(0)
''//////////////////Hold-Position Sequence///////////////////////////
'Decision_Engine(2) 'Hover, get initial IR distance readings, and Pause to stabalize
''//////////////////Start Of Auto Routines/////////////////////////// }
REPEAT WHILE startbtn == 1
Repeat 150
FC.PID(0,0,0,0,0,Thrust)
Get_Data
Motor_Output
TestDisplay
IF Accel_X > 10 OR Accel_Y > 10
ShutDownProcess
Decision_Engine(1)
ShutDownProcess
IF INA[15] == 1
Delay_MS(500)
IF INA[15] == 1
Delay_MS(500)
IF INA[15] == 1
startbtn := 0
IF startbtn == 0
ShutDownProcess
PUB Decision_Engine(Control_Variable) | Command
'''''''''''''''''''''''Decision ENGINE''''''''''''''''''''''''''''''''''''''''''''''''''
'''FC.PID(Pitch_Orient, Roll_Orient, Rotate_Command, Alt_Command) ''''
Has_Control := Control_Variable
CASE Has_Control
0: 'Take Off
cur_State := string("Taking-Off")
SP_Alt := 30
Thrust := 10
SIR.Get_IR
REPEAT WHILE SIR.IR_BO > 22
SIR.Get_IR
Thrust := Thrust + 1
FC.PID(0,0,0,0,0,Thrust)
REPEAT UNTIL SIR.IR_BO > (SP_Alt - 5) AND SIR.IR_BO < (SP_Alt + 5)
SIR.Get_IR
IF SIR.IR_BO < (SP_Alt - 5)
Thrust := Thrust + 1
FC.PID(0,0,0,0,0,Thrust)
ELSEIF SIR.IR_BO > (SP_Alt + 5)
Thrust := (Thrust - 1) #>0
FC.PID(0,0,0,0,0,Thrust)
1: 'Land
cur_State := string("Landing")
REPEAT UNTIL Thrust == 0
SIR.Get_IR
Alt := SIR.IR_BO
Delay_MS(50)
FC.PID(0,0,0,0,0,(Thrust - 10))
FC.PID(0,0,0,0,0,0)
ShutDownProcess
2: 'Hold Position
cur_State := string("Hovering")
FC.PID(0,0,0,0,0,Thrust)
SIR.Get_IR
IF SIR.IR_FR < 50
FC.PID(Auto_Reverse, 0,0,0, 0, Thrust)
IF SIR.IR_LE < 50
FC.PID(0, Auto_Right,0,0, 0, Thrust)
IF SIR.IR_RI < 50
FC.PID(0, Auto_Left,0,0, 0, Thrust)
IF SIR.IR_RR < 50
FC.PID(Auto_Forward, 0,0,0, 0, Thrust)
IF SIR.IR_TO < 25
Thrust := (Thrust - 5)#>0
FC.PID(0, 0,0,0, 0, Thrust)
'IF SIR.IR_BO < 30
'Thrust := Thrust + 5
'FC.PID(0, 0,0,0, 0, Thrust)
3: 'Rotate
cur_State := string("Rotating")
IF Heading > (SP_Head + 20)
Command := 50
FC.PID(0,0,0,0,Command,Thrust)
ElseIF Heading < (SP_Head - 20)
Command := -50
FC.PID(0,0,0,0,Command,Thrust)
ELSE
Command := 0
FC.PID(0,0,0,0,Command,Thrust)
4: 'Move Forward
cur_State := string("Forward")
IF FR_IR > 20
FC.PID(Auto_Forward,0,0,0,0,0)
Else
FC.PID(0,0,0,0,0,Thrust)
'Error_Status := 1
5: 'Move Reverse
cur_State := string("Reverse")
IF RR_IR > 20
FC.PID(Auto_Reverse,0,0,0,0,Thrust)
Else
FC.PID(0,0,0,0,0,Thrust)
6: 'Move Left
cur_State := string("Left")
IF LE_IR > 20
FC.PID(0,Auto_Left,0,0,0,Thrust)
Else
FC.PID(0,0,0,0,0,Thrust)
7: 'Move Right
cur_State := string("Right")
IF RI_IR > 20
FC.PID(0,Auto_Right,0,0,0,Thrust)
Else
FC.PID(0,0,0,0,0,Thrust)
8: 'Change Altitude
cur_State := string("Throttle")
FC.PID(0,0,0,0,0,Thrust)
9: 'Slow Start
cur_State := string("Slow Start")
Repeat Until Thrust == 375
Thrust := Thrust + 5
FC.PID(0,0,0,0,0,Thrust)
Get_Data
Motor_Output
TestDisplay
Delay_MS(50)
'Delay_MS(3000)
'ShutDownProcess
PUB Motor_Output
ESC.Servo(Mtr_A, M_A) 'SEND OUTPUT PULSE TO MOTORS A, B, C, AND D
ESC.Servo(Mtr_B, M_B)
ESC.Servo(Mtr_C, M_C)
ESC.Servo(Mtr_D, M_D)
PUB ShutDownProcess
M_A := M_B := M_C := M_D := 1000
Motor_Output
ESC.Stop
FC.Stop
SIR.Stop
Clk.Init(5_000_000)
Clk.SetClock(RCSLOW)
REPEAT
IF INA[15] == 1
REBOOT
PUB Get_Heading | n, avgvalue
IF avgvalue > 350 OR avgvalue < 10
Bearing := string("North")
ELSEIF avgValue > 11 AND avgvalue < 31
Bearing := string("North-East")
ELSEIF avgValue > 32 AND avgvalue < 52
Bearing := string("East")
ELSEIF avgValue > 53 AND avgvalue < 73
Bearing := string("South-East")
ELSEIF avgValue > 74 AND avgvalue < 94
Bearing := string("South")
ELSEIF avgValue > 95 AND avgvalue < 115
Bearing := string("South-West")
ELSEIF avgValue > 116 AND avgvalue < 136
Bearing := string("West")
ELSEIF avgValue > 137 AND avgvalue < 157
Bearing := string("North-West")
ELSE
Bearing := string("Unknown")
return avgvalue
PUB Get_Data
Gx_F := FC.GetGX
Gy_F := FC.GetGY
Gz_F := FC.GetGZ
Accel_X := FC.GetAccelX
Accel_Y := FC.GetAccelY
'RawAx := FC.GetRawAx
'RawAy := FC.GetRawAy
'RawAz := FC.GetRawAz
'Ax := FC.GetAx
'Ay := FC.GetAy
'Az := FC.GetAz
Heading := FC.GetHeading
count := FC.Return_Count
M_A := FC.Motor_A
M_B := FC.Motor_B
M_C := FC.Motor_C
M_D := FC.Motor_D
SIR.Get_IR
FR_IR := SIR.IR_FR
RR_IR := SIR.IR_RR
LE_IR := SIR.IR_LE
RI_IR := SIR.IR_RI
TO_IR := SIR.IR_TO
BO_IR := SIR.IR_BO
{PUB Comm
'''''''''''''''''''''''INTER-PROPELLER COMMUNICATION''''''''''''''''''''''''''''''''''''''''''''''''''
Repeat
TxRx.str(string("!v=")) 'SEND SYNC PULSE
Repeat i from 0 to 8 'SEND ENTIRE BUFFER
TxRx.PutLong(SenBuffer[i])
TxRx.waitstr(string("!x*"), clkfreq*20)
Repeat i from 0 to 8 'RECEIVE ENTIRE BUFFER
RecBuffer[i] := TxRx.GetLong
IF RecBuffer[0] <>= 9847_3817
'COGINIT(7, Comm, @stack[0])
RecBuffer[1] := User_Control
RecBuffer[2] := Received_Commands
RecBuffer[3] := 0
RecBuffer[4] := 0
RecBuffer[5] := 0
RecBuffer[6] := 0
RecBuffer[7] := 0
RecBuffer[8] := 0
RecBuffer[9] := 0
SenBuffer[0] := Gx_F
SenBuffer[1] := Gy_F
SenBuffer[2] := Gz_F
SenBuffer[3] := Ax
SenBuffer[4] := Ay
SenBuffer[5] := Az
SenBuffer[6] := 0
SenBuffer[7] := 0
SenBuffer[8] := Heading
SenBuffer[9] := cur_State
}
PUB TestDisplay | tempe
Ser.tx(CLS)
Ser.str(string("Test Data From UAV..............", CR))
ser.str(string("Cogs Free: "))
ser.dec(SCG.freecount)
ser.tx(13)
Ser.str(string(CR, "Start Button : "))
ser.dec(startbtn)
Ser.str(string(CR, "INA pin 15 : "))
ser.dec(INA[15])
ser.str(string(CR, CR, "GX : "))
ser.dec(Gx_F)
Ser.str(string(CR, "GY : "))
ser.dec(Gy_F)
Ser.str(string(CR, "GZ : "))
ser.dec(Gz_F)
Ser.str(string(CR, CR, "Pitch : "))
ser.dec(Accel_X)
Ser.str(string(CR, "Roll : "))
ser.dec(Accel_Y)
{ Ser.str(string(CR, CR, "Raw Ax : "))
ser.dec(RawAx)
Ser.str(string(CR, "Raw Ay : "))
ser.dec(RawAy)
Ser.str(string(CR, "Raw Az : "))
ser.dec(RawAz)
Ser.str(string(CR, CR, "Ax : "))
ser.dec(Ax)
Ser.str(string(CR, "Ay : "))
ser.dec(Ay)
Ser.str(string(CR, "Az : "))
ser.dec(Az)
SER.str(string(CR,CR, "Heading: "))
ser.dec(Heading)
Ser.tx(CR) }
Ser.str(string(CR, "Motor A: "))
ser.dec(M_A)
Ser.str(string(CR, "Motor B: "))
ser.dec(M_B)
Ser.str(string(CR, "Motor C: "))
ser.dec(M_C)
Ser.str(string(CR, "Motor D: "))
ser.dec(M_D)
ser.tx(CR)
ser.dec(count)
Delay_MS(100)
' /////////////////////////////////////////////////////////////////////////////
PUB Delay_MS( time )
{{
Delays "time" milliseconds and returns.
}}
waitcnt (cnt + time*CLOCKS_PER_MILLISECOND)
' // end Delay_MS
' /////////////////////////////////////////////////////////////////////////////
PUB Delay_US( time )
{{
Delays "time" microseconds and returns.
}}
waitcnt (cnt + time*CLOCKS_PER_MICROSECOND)
' // end Delay_US
'//////////////////////////////////////////////////////////////////////////////
{{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}}
'' From Parallax Inc. Propeller Education Kit - Objects Lab v1.1
{{
────────────────────────────────────────────────────────────────────────────────────────
File: FullDuplexSerialPlus.spin
Version: 1.2
Copyright (c) 2008 Parallax, Inc.
See end of file for terms of use.
This is the FullDuplexSerial object v1.1 from the Propeller Tool's Library
folder with modified documentation and methods for converting text strings
into numeric values in several bases.
v1.2 - Added WaitStr, GetLong and PutLong methods.
────────────────────────────────────────────────────────────────────────────────────────
}}
CON ''
''Parallax Serial Terminal Control Character Constants
''────────────────────────────────────────────────────
HOME = 1 ''HOME = 1
CRSRXY = 2 ''CRSRXY = 2
CRSRLF = 3 ''CRSRLF = 3
CRSRRT = 4 ''CRSRRT = 4
CRSRUP = 5 ''CRSRUP = 5
CRSRDN = 6 ''CRSRDN = 6
BELL = 7 ''BELL = 7
BKSP = 8 ''BKSP = 8
TAB = 9 ''TAB = 9
LF = 10 ''LF = 10
CLREOL = 11 ''CLREOL = 11
CLRDN = 12 ''CLRDN = 12
CR = 13 ''CR = 13
CRSRX = 14 ''CRSRX = 14
CRSRY = 15 ''CRSRY = 15
CLS = 16 ''CLS = 16
VAR
long cog 'cog flag/id
long rx_head '9 contiguous longs
long rx_tail
long tx_head
long tx_tail
long rx_pin
long tx_pin
long rxtx_mode
long bit_ticks
long buffer_ptr
byte rx_buffer[16] 'transmit and receive buffers
byte tx_buffer[16]
PUB start(rxpin, txpin, mode, baudrate) : okay
{{
Starts serial driver in a new cog
rxpin - input receives signals from peripheral's TX pin
txpin - output sends signals to peripheral's RX pin
mode - bits in this variable configure signaling
bit 0 inverts rx
bit 1 inverts tx
bit 2 open drain/source tx
bit 3 ignor tx echo on rx
baudrate - bits per second
okay - returns false if no cog is available.
}}
stop
longfill(@rx_head, 0, 4)
longmove(@rx_pin, @rxpin, 3)
bit_ticks := clkfreq / baudrate
buffer_ptr := @rx_buffer
okay := cog := cognew(@entry, @rx_head) + 1
PUB stop
'' Stops serial driver - frees a cog
if cog
cogstop(cog~ - 1)
longfill(@rx_head, 0, 9)
PUB tx(txbyte)
'' Sends byte (may wait for room in buffer)
repeat until (tx_tail <> (tx_head + 1) & $F)
tx_buffer[tx_head] := txbyte
tx_head := (tx_head + 1) & $F
if rxtx_mode & %1000
rx
PUB rx : rxbyte
'' Receives byte (may wait for byte)
'' rxbyte returns $00..$FF
repeat while (rxbyte := rxcheck) < 0
PUB rxflush
'' Flush receive buffer
repeat while rxcheck => 0
PUB rxcheck : rxbyte
'' Check if byte received (never waits)
'' rxbyte returns -1 if no byte received, $00..$FF if byte
rxbyte--
if rx_tail <> rx_head
rxbyte := rx_buffer[rx_tail]
rx_tail := (rx_tail + 1) & $F
PUB rxtime(ms) : rxbyte | t
'' Wait ms milliseconds for a byte to be received
'' returns -1 if no byte received, $00..$FF if byte
t := cnt
repeat until (rxbyte := rxcheck) => 0 or (cnt - t) / (clkfreq / 1000) > ms
PUB str(stringptr)
'' Send zero terminated string that starts at the stringptr memory address
repeat strsize(stringptr)
tx(byte[stringptr++])
PUB getstr(stringptr) | index
'' Gets a carriage return terminated string and stores it (starting at stringptr)
'' as a zero terminated string
index~
repeat until ((byte[stringptr][index++] := rx) == 13)
byte[stringptr][--index]~
PUB waitstr(stringptr, timeout) : success | index, tstart
{{Wait for a string.
Parameters:
stringptr - points to the comparison string
timeout - number of clock ticks to wait
Returns:
success - true if string found or false if timeout exceeded
}}
tstart := cnt
index~
repeat until byte[stringptr][index] == 0
if (cnt - tstart) > timeout
return
if byte[stringptr][index] == rx
index++
else
index~
success~~
PUB dec(value) | i
'' Prints a decimal number
if value < 0
-value
tx("-")
i := 1_000_000_000
repeat 10
if value => i
tx(value / i + "0")
value //= i
result~~
elseif result or i == 1
tx("0")
i /= 10
PUB GetDec : value | tempstr[11]
'' Gets decimal character representation of a number from the terminal
'' Returns the corresponding value
GetStr(@tempstr)
value := StrToDec(@tempstr)
PUB StrToDec(stringptr) : value | char, index, multiply
'' Converts a zero terminated string representation of a decimal number to a value
value := index := 0
repeat until ((char := byte[stringptr][index++]) == 0)
if char => "0" and char =< "9"
value := value * 10 + (char - "0")
if byte[stringptr] == "-"
value := - value
PUB bin(value, digits)
'' Sends the character representation of a binary number to the terminal.
value <<= 32 - digits
repeat digits
tx((value <-= 1) & 1 + "0")
PUB GetBin : value | tempstr[11]
'' Gets binary character representation of a number from the terminal
'' Returns the corresponding value
GetStr(@tempstr)
value := StrToBin(@tempstr)
PUB StrToBin(stringptr) : value | char, index
'' Converts a zero terminated string representaton of a binary number to a value
value := index := 0
repeat until ((char := byte[stringptr][index++]) == 0)
if char => "0" and char =< "1"
value := value * 2 + (char - "0")
if byte[stringptr] == "-"
value := - value
PUB hex(value, digits)
'' Print a hexadecimal number
value <<= (8 - digits) << 2
repeat digits
tx(lookupz((value <-= 4) & $F : "0".."9", "A".."F"))
PUB GetHex : value | tempstr[11]
'' Gets hexadecimal character representation of a number from the terminal
'' Returns the corresponding value
GetStr(@tempstr)
value := StrToHex(@tempstr)
PUB StrToHex(stringptr) : value | char, index
'' Converts a zero terminated string representaton of a hexadecimal number to a value
value := index := 0
repeat until ((char := byte[stringptr][index++]) == 0)
if (char => "0" and char =< "9")
value := value * 16 + (char - "0")
elseif (char => "A" and char =< "F")
value := value * 16 + (10 + char - "A")
elseif(char => "a" and char =< "f")
value := value * 16 + (10 + char - "a")
if byte[stringptr] == "-"
value := - value
PUB GetLong : value | index
''Receives four serial bytes that comprise an int in little endian format.
''Returns an int.
repeat index from 0 to 3
byte[@value][index]:=rx
PUB PutLong(value) | index
''Transmits and int value parameter as four bytes in little endian format.
repeat index from 0 to 3
tx(byte[@value][index])
DAT
'***********************************
'* Assembly language serial driver *
'***********************************
org
'
'
' Entry
'
entry mov t1,par 'get structure address
add t1,#4 << 2 'skip past heads and tails
rdlong t2,t1 'get rx_pin
mov rxmask,#1
shl rxmask,t2
add t1,#4 'get tx_pin
rdlong t2,t1
mov txmask,#1
shl txmask,t2
add t1,#4 'get rxtx_mode
rdlong rxtxmode,t1
add t1,#4 'get bit_ticks
rdlong bitticks,t1
add t1,#4 'get buffer_ptr
rdlong rxbuff,t1
mov txbuff,rxbuff
add txbuff,#16
test rxtxmode,#%100 wz 'init tx pin according to mode
test rxtxmode,#%010 wc
if_z_ne_c or outa,txmask
if_z or dira,txmask
mov txcode,#transmit 'initialize ping-pong multitasking
'
'
' Receive
'
receive jmpret rxcode,txcode 'run chunk of tx code, then return
test rxtxmode,#%001 wz 'wait for start bit on rx pin
test rxmask,ina wc
if_z_eq_c jmp #receive
mov rxbits,#9 'ready to receive byte
mov rxcnt,bitticks
shr rxcnt,#1
add rxcnt,cnt
:bit add rxcnt,bitticks 'ready next bit period
:wait jmpret rxcode,txcode 'run chunk of tx code, then return
mov t1,rxcnt 'check if bit receive period done
sub t1,cnt
cmps t1,#0 wc
if_nc jmp #:wait
test rxmask,ina wc 'receive bit on rx pin
rcr rxdata,#1
djnz rxbits,#:bit
shr rxdata,#32-9 'justify and trim received byte
and rxdata,#$FF
test rxtxmode,#%001 wz 'if rx inverted, invert byte
if_nz xor rxdata,#$FF
rdlong t2,par 'save received byte and inc head
add t2,rxbuff
wrbyte rxdata,t2
sub t2,rxbuff
add t2,#1
and t2,#$0F
wrlong t2,par
jmp #receive 'byte done, receive next byte
'
'
' Transmit
'
transmit jmpret txcode,rxcode 'run chunk of rx code, then return
mov t1,par 'check for head <> tail
add t1,#2 << 2
rdlong t2,t1
add t1,#1 << 2
rdlong t3,t1
cmp t2,t3 wz
if_z jmp #transmit
add t3,txbuff 'get byte and inc tail
rdbyte txdata,t3
sub t3,txbuff
add t3,#1
and t3,#$0F
wrlong t3,t1
or txdata,#$100 'ready byte to transmit
shl txdata,#2
or txdata,#1
mov txbits,#11
mov txcnt,cnt
:bit test rxtxmode,#%100 wz 'output bit on tx pin
test rxtxmode,#%010 wc 'according to mode
if_z_and_c xor txdata,#1
shr txdata,#1 wc
if_z muxc outa,txmask
if_nz muxnc dira,txmask
add txcnt,bitticks 'ready next cnt
:wait jmpret txcode,rxcode 'run chunk of rx code, then return
mov t1,txcnt 'check if bit transmit period done
sub t1,cnt
cmps t1,#0 wc
if_nc jmp #:wait
djnz txbits,#:bit 'another bit to transmit?
jmp #transmit 'byte done, transmit next byte
'
'
' Uninitialized data
'
t1 res 1
t2 res 1
t3 res 1
rxtxmode res 1
bitticks res 1
rxmask res 1
rxbuff res 1
rxdata res 1
rxbits res 1
rxcnt res 1
rxcode res 1
txmask res 1
txbuff res 1
txdata res 1
txbits res 1
txcnt res 1
txcode res 1
{{
┌──────────────────────────────────────────────────────────────────────────────────────┐
│ 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. │
└──────────────────────────────────────────────────────────────────────────────────────┘
}}
{{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}}
{{
Flight Control
Controls Wii Nunchuck, Wii MotionPlus, Digital Compass, ESC outputs, Kalman Filter, and PID loop.
Uses 3 cogs.
}}
CON
CLOCKS_PER_MICROSECOND = 5*16 ' simple xin*pll / 1_000_000
CLOCKS_PER_MILLISECOND = 5000*16 ' simple xin*pll / 1_000
HzToSec = 1.0 / 80_000_000.0
F_Mode = 4.54545454545 ' fast rotation
G_Mode = 0.111 ' slow rotation for gyro, decreases sample speed
A_Mode = 0.25 'slow rotation for accel, decreases sample speed
Deg_Sec = 594.7 'the sensetivity of the Wii Motionplus
aslope = 2.8571 'scaling used for accel, convert to g's
aoffset = -1000.0 'offset used for accel, convert to g's
''/////////////Tune the Following values for the PID loop and the Kalman Filter///////////////////////
slope = 0 'FLOATING POINT VALUE scale input values to outputs
offset = 1150
slopeY = 0.004899 'used to convert the Wii Nunchuck raw values to radians
slopeX = 0.004800
slopeZ = 0.004854
offsetY = -2.521583
offsetX = -2.445026
offsetZ = -2.452766
Accel_Noise = 0.00003 'expected accel noise
Accel_Trust = 0.00003 'how much to trust the accel
Gyro_Trust = 0.000001 'how much to trust the gyro
''ALL REMAINING FLOATING POINT VALUES
Proportional_Gain = 1 'floating point
Integral_Gain = 0.0 'fixed point
Derivative_Gain = 0.0 'floating point
Mtr_A = 0 'ESC pins
Mtr_B = 1
Mtr_C = 2
Mtr_D = 3
VAR
long stack[700] 'stack space for PID loop cog
''/////////////////////////Variables for the Wii sensor measurements////////////////////////////
long Ay, Ax, Az, GZ 'conditioned accel and gyro data
long RawAx, RawAy, RawAz 'raw accel and gyro data
long RawGx, RawGy, RawGz
long Cgx, Cgy, Cgz, Cax, Cay, Caz, Cap, Car 'inital center values of gyro and accel
long AccXCen, AccYCen
''/////////////////////Variables for the Kalman Filter//////////////////////////
long xAngleInt, yAngleInt
long CurTime, PrevTime, DeltaTime, fDelta
long AccelX, AccelY, fAccelX, fAccelY
long GyroX, GyroY, fGyroX, fGyroY
long GyroXUnb, GyroYUnb, fGyroXUnb, fGyroYUnb
long xAngle, xBias
long xP00, xP01, xP10, xP11
long yAngle, yBias
long yP00, yP01, yP10, yP11
long QAngle, QGyro, RAngle
''/////////////////////////Variables for the PID loop////////////////////////////
long SP[5], b, m, Kp, Ki, Kd 'gains
long M_A, M_B, M_C, M_D 'motor outputs
long Auto_Pitch, Auto_Roll 'command given roll and pitch
long Acc_Pitch, Acc_Roll 'instaneous roll and pitch
long SP_R, RawCompass, Alt 'rotate, compass, and altitude
long count
byte cog1, cog2
OBJ
IMU : "I2C_Wii"
f : "F32"
ESC : "ESC_Spin"
PUB Stop 'stop all objects and cogs
M_A := M_B := M_C := M_D := 1000
ESC.Stop
f.Stop
cogstop(cog1)
'cogstop(cog2)
PUB Init(SCL, SDA, IMU_Mode)
IMU.init( SCL,SDA,IMU_Mode ) ' Initialize the accelerometer / gyro
IMU.enable ' Enable the Wii
f.start ' Start the F32 Floating Point Math object
KalmanInit( Accel_Trust, Gyro_Trust, Accel_Noise ) 'initialize the kalman filter
xAngleInt := yAngleInt := 0
Delay_MS(500) ' Wait 1/2 second before initializing gyros
''/////////Acquire some center values//////////////////////
IMU.readData 'Must be called before measurements are received
IMU.readData 'retrieve center values
Cgx := IMU.rotate_x
Cgy := IMU.rotate_y
Cgz := IMU.rotate_z
Cax := IMU.accelX
Cay := IMU.accelY
Caz := IMU.accelZ
Gyro_Accel_Compass_Calibrate
PrevTime := cnt
SP[0] := 0 'initialize set points to zero
SP[1] := 0
SP[2] := 0
SP[3] := 0
SP[4] := 0
m := slope 'SET CONDITIONS AND GAINS. ONLY M AND KI ARE FLOATING POINT
b := offset
Kp := Proportional_Gain
Ki := Integral_Gain
Kd := Derivative_Gain
Delay_MS(3000)
cog1 := cognew(Flight_Control, @stack[0]) 'start new cog for PID loop
return
PUB Motor_Start
ESC.Start 'start ESC object
Delay_MS(1000)
'''''''''''''''''''''''INITIALIZE ESC's AND MOTOR's''''''''''''''''''''''
ESC.Servo(Mtr_A,1000) 'initialize ESC's by sending a low pulse to each
ESC.Servo(Mtr_B,1000)
ESC.Servo(Mtr_C,1000)
ESC.Servo(Mtr_D,1000)
Delay_MS(500)
return
PUB Motor_Run
ESC.Servo(Mtr_A, M_A) 'SEND OUTPUT PULSE TO MOTORS A, B, C, AND D
ESC.Servo(Mtr_B, M_B)
ESC.Servo(Mtr_C, M_C)
ESC.Servo(Mtr_D, M_D)
PUB PID(Pitch_Orient, Roll_Orient, SP_Pitch, SP_Roll, Rotate_Command, Alt_Command)
SP[0] := 0 'SAVE SETPOINTS AND FEEDBACK SIGNAL IN SPECIFIED ARRAY SLOTS
SP[1] := 0
SP[2] := 0
SP[3] := 0 'SP_Pitch
SP[4] := 0 'SP_Roll
Auto_Pitch := Pitch_Orient
Auto_Roll := Roll_Orient
SP_R := Rotate_Command
Alt := Alt_Command
return
PUB Flight_Control | GX_PID, GY_PID, GZ_PID, AccX_PID, AccY_PID, FB[5], Pre_Error[5], e[5], P[5], I[5], D[5], dt[5], pre_dt[5], j
'''''''''''''''''''''''FLIGHT CONTROL AND PID LOOP''''''''''''''''''''''''''''''''''''''''''''''''''
Update 'first get initial sensor values
Pre_Error[0] := SP[0] - GyroXUnb 'set first error
Pre_Error[1] := SP[1] - GyroYUnb
Pre_Error[2] := SP[2] - GZ
Pre_Error[3] := SP[3] - Acc_Pitch
Pre_Error[4] := SP[4] - Acc_Roll
I[0] := 0 'initialize integral to zero
I[1] := 0
I[2] := 0
I[3] := 0
I[4] := 0
Repeat
Update 'get sensor values
FB[0] := GyroXUnb 'GX
FB[1] := GyroYUnb 'GY
FB[2] := 0 'GZ
FB[3] := Acc_Pitch
FB[4] := Acc_Roll
Repeat j from 0 to 4
dt[j] := cnt 'get system counter values
dt[j] := f.FSub(f.FFloat(dt[j]), pre_dt[j]) 'get difference of previous counter and current counter
dt[j] := f.FMul(f.FFloat(dt[j]), HzToSec) 'convert system counts to sec
e[j] := SP[j] - FB[j] 'error = set point - feedback
I[j] := I[j] + (f.FRound(f.FMul(f.FFloat(e[j]), dt[j]))) 'integral
D[j] := f.FRound(f.FDiv(f.FSub(f.FFloat(e[j]), Pre_Error[j]), dt[j])) 'derrivative
Pre_Error[j] := e[j] 'previous error
pre_dt[j] := dt[j] 'previous time
GX_PID := (Kp * e[0]) + (f.FRound(f.FMul(Ki, f.FFloat(I[0])))) + (f.FRound(f.FMul(Kd, f.FFloat(D[0])))) 'multiply by gain values for each
GY_PID := (Kp * e[1]) + (f.FRound(f.FMul(Ki, f.FFloat(I[1])))) + (f.FRound(f.FMul(Kd, f.FFloat(D[0])))) 'pid loop
GZ_PID := (Kp * e[2]) + (f.FRound(f.FMul(Ki, f.FFloat(I[2])))) + (f.FRound(f.FMul(Kd, f.FFloat(D[0]))))
AccX_PID := (Kp * e[3]) + (f.FRound(f.FMul(Ki, f.FFloat(I[3])))) + (f.FRound(f.FMul(Kd, f.FFloat(D[0]))))
AccY_PID := (Kp * e[4]) + (f.FRound(f.FMul(Ki, f.FFloat(I[4])))) + (f.FRound(f.FMul(Kd, f.FFloat(D[0]))))
''/////////////////////////Motor Outputs for a + configuration//////////////////////////////////////////////
M_A := ((-GY_PID + GZ_PID - AccX_PID - Auto_Pitch + SP_R + Alt + b)#>1100)<#2000 'add all to motor outputs
M_B := ((GX_PID - GZ_PID - AccY_PID + Auto_Roll - SP_R + Alt + b)#>1100)<#2000
M_C := ((GY_PID + GZ_PID + AccX_PID + Auto_Pitch + SP_R + Alt + b)#>1100)<#2000
M_D := ((-GX_PID - GZ_PID + AccY_PID - Auto_Roll - SP_R + Alt + b)#>1100)<#2000
'Motor_Run 'output to motors
count ++
PUB Update | radius
''/////////////Read in Wii MotionPlus and Wii Nunchuck Raw Values/////////////////////
IMU.readData
IMU.readData
RawAx := IMU.accelX
RawAy := IMU.accelY
RawAz := IMU.accelZ
RawGx := IMU.rotate_x
RawGy := IMU.rotate_y
RawGz := IMU.rotate_z
RawCompass := IMU.Digital_Compass
'''////////////////////////convert gyro data to deg/sec, Make sure to select fast mode or slow mode/////////////////////
GZ := f.FRound(f.FMul(f.FDiv(f.FSub(f.FFloat(Cgz), f.FFloat(RawGz)), f.FDiv(f.FFloat(Cgz), Deg_Sec) ), G_Mode))
Ax := f.FRound(f.FAdd(f.FMul(f.FFloat(RawAx), aslope), aoffset))
Ay := f.FRound(f.FAdd(f.FMul(f.FFloat(RawAy), aslope), aoffset))
Az := f.FRound(f.FAdd(f.FMul(f.FFloat(RawAz), aslope), aoffset))
radius := ^^((Ax)*(Ax) + (Az)*(Az) + (Ay)*(Ay))
Acc_Pitch := f.FRound(f.FMul(f.Degrees(f.ACos(f.FDiv(f.FFloat(Ay),f.FFloat(radius)))), A_Mode))-AccXCen
Acc_Roll := f.FRound(f.FMul(f.Degrees(f.ATan2(f.FFloat(Ax), f.FFloat(Az))), A_Mode))-AccYCen
''///////////////Convert Accelerometer and Gyro readings to radians///////////////////////
fAccelX := f.Radians(f.FAdd(f.FMul(slopeX, f.FFloat(RawAx)), offsetX))
fAccelY := f.Radians(f.FAdd(f.FMul(slopeY, f.FFloat(RawAy)), offsetY))
fGyroX := f.Radians(f.FMul(f.FDiv(f.FSub(f.FFloat(Cgx), f.FFloat(RawGx)), f.FDiv(f.FFloat(Cgx), Deg_Sec) ), G_Mode))
fGyroY := f.Radians(f.FMul(f.FDiv(f.FSub(f.FFloat(Cgy), f.FFloat(RawGy)), f.FDiv(f.FFloat(Cgy), Deg_Sec) ), G_Mode))
'Compute the time delta
CurTime := cnt
DeltaTime := CurTime - PrevTime
PrevTime := CurTime
'Convert the delta to seconds
fDelta := f.FFloat(DeltaTime)
fDelta := f.FMul(fDelta, HzToSec)
'Run the filter
KalmanPredict( fGyroX, fGyroY, fDelta )
KalmanUpdate( fAccelX, fAccelY )
'Produce the results
xAngleInt := f.FRound(f.Degrees(xAngle))
yAngleInt := f.FRound(f.Degrees(yAngle))
GyroXUnb := f.FRound(f.Degrees(fGyroXUnb))
GyroYUnb := f.FRound(f.Degrees(fGyroYUnb))
return
''//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
''////////Below is Code from the OBEX, I will be going through it and possible condensing/changing some of the math/////////////////
PRI KalmanInit( QAngle_, QGyro_, RAngle_ )
{{ Initialize a new Kalman Filter object }}
{{ QAngle = Trust in accelerometer measurements }}
{{ QGyro = Trust in gyro measurements }}
{{ RAngle = expected noise in accelerometer measurements in radians }}
xAngle := xBias := GyroXUnb := GyroYUnb := 0
xP00 := xP01 := xP10 := xP11 := 0
yAngle := yBias := 0
yP00 := yP01 := yP10 := yP11 := 0
QAngle := QAngle_
QGyro := QGyro_
RAngle := RAngle_
return
PRI KalmanPredict( RateX, RateY, dt ) | temp, negdt, negdtXp11, negdtYp11, QAngledt, QGyrodt
negdt := f.FNeg(dt)
negdtXp11 := f.FMul(negdt, xP11)
negdtYp11 := f.FMul(negdt, yP11)
QAngledt := f.FMul(QAngle, dt)
QGyrodt := f.FMul(QGyro, dt)
'xAngle += dt * (x - xBias );
fGyroXUnb := f.FSub(RateX, xBias)
xAngle := f.FAdd( xAngle, f.FMul(dt, fGyroXUnb) )
'xP00 += -dt * (xP10 + xP01) + xQ_angle * dt;
xP00 := f.FAdd( xP00, f.FAdd(f.FMul(negdt, f.FAdd(xP10, xP01)), QAngledt))
'xP01 += -dt * xP_11;
xP01 := f.FAdd(xP01, negdtXp11)
'xP10 += -dt * xP_11;
xP10 := f.FAdd(xP10, negdtXp11)
'xP11 += xQ_gyro * dt;
xP11 := f.FAdd(xP11, QGyrodt)
'yAngle += dt * (y - yBias );
fGyroYUnb := f.FSub(RateY, yBias)
yAngle := f.FAdd( yAngle, f.FMul(dt, fGyroYUnb) )
'yP00 += -dt * (yP10 + yP01) + QAngle * dt;
yP00 := f.FAdd( yP00, f.FAdd(f.FMul(negdt, f.FAdd(yP10, yP01)), QAngledt))
'yP01 += -dt * yP_11;
yP01 := f.FAdd(yP01, negdtYp11)
'yP10 += -dt * yP_11;
yP10 := f.FAdd(yP10, negdtYp11)
'xP11 += xQ_gyro * dt;
yP11 := f.FAdd(yP11, QGyrodt)
return
PRI KalmanUpdate( AngleX , AngleY ) | err, S, K0, K1
err := f.FSub(AngleX, xAngle)
S := f.FAdd(xP00, RAngle)
K0 := f.FDiv(xP00, S)
K1 := f.FDiv(xP10, S)
xAngle := f.FAdd(xAngle, f.FMul(K0, err))
xBias := f.FAdd( xBias, f.FMul(K1, err))
xP10 := f.FSub(xP10, f.FMul(K1, xP00))
xP11 := f.FSub(xP11, f.FMul(K1, xP01))
xP00 := f.FSub(xP00, f.FMul(K0, xP00))
xP01 := f.FSub(xP01, f.FMuL(K0, xP01))
err := f.FSub(AngleY, yAngle)
S := f.FAdd(yP00, RAngle)
K0 := f.FDiv(yP00, S)
K1 := f.FDiv(yP10, S)
yAngle := f.FAdd(yAngle, f.FMul(K0, err))
yBias := f.FAdd( yBias, f.FMul(K1, err))
yP10 := f.FSub(yP10, f.FMul(K1, yP00))
yP11 := f.FSub(yP11, f.FMul(K1, yP01))
yP00 := f.FSub(yP00, f.FMul(K0, yP00))
yP01 := f.FSub(yP01, f.FMul(K0, yP01))
return
PUB Gyro_Accel_Compass_Calibrate | radius_pre
IMU.readData
IMU.readData
RawAx := IMU.accelX
RawAy := IMU.accelY
RawAz := IMU.accelZ
Ax := f.FRound(f.FAdd(f.FMul(f.FFloat(RawAx), aslope), aoffset))
Ay := f.FRound(f.FAdd(f.FMul(f.FFloat(RawAy), aslope), aoffset))
Az := f.FRound(f.FAdd(f.FMul(f.FFloat(RawAz), aslope), aoffset))
radius_pre := ^^((Ax)*(Ax) + (Az)*(Az) + (Ay)*(Ay))
AccXCen := f.FRound(f.FMul(f.Degrees(f.ACos(f.FDiv(f.FFloat(Ay),f.FFloat(radius_pre)))), A_Mode))
AccYCen := f.FRound(f.FMul(f.Degrees(f.ATan2(f.FFloat(Ax), f.FFloat(Az))), A_Mode))
PUB GetRawGX
return RawGx
PUB GetRawGY
return RawGy
PUB GetRawGZ
return RawGz
PUB GetGZ
return GZ
PUB GetfGyroX
return fGyroX
PUB GetfGyroY
return fGyroY
PUB GetfAccelX
return fAccelX
PUB GetfAccelY
return fAccelY
PUB GetxAngle
return xAngleInt
PUB GetyAngle
return yAngleInt
PUB GetGyroXUnb
return GyroXUnb
PUB GetGyroYUnb
return GyroYUnb
PUB GetAccelX
return Acc_Pitch
PUB GetAccelY
return Acc_Roll
PUB GetHeading
return RawCompass / 10
PUB Motor_A
return M_A
PUB Motor_B
return M_B
PUB Motor_C
return M_C
PUB Motor_D
return M_D
PUB Return_Count
return count
PUB Delay_MS( time )
{{
Delays "time" milliseconds and returns.
}}
waitcnt (cnt + time*CLOCKS_PER_MILLISECOND)
' // end Delay_MS
' /////////////////////////////////////////////////////////////////////////////
PUB Delay_US( time )
{{
Delays "time" microseconds and returns.
}}
waitcnt (cnt + time*CLOCKS_PER_MICROSECOND)
' // end Delay_US
'//////////////////////////////////////////////////////////////////////////////
{{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}}
{{ This Object is the combination of several objects, Wii MotionPlus w/ Nunchuck, HMC6352 Digital Compass,
and the I2C routines needed to R/W those devices.
All commments are from the original objects.
}}
CON
_i2cNAK = 1
_i2cACK = 0
_PinHigh = 1
_PinLow = 0
_i2cByteAddress = 8
_i2cWordAddress = 16
' arbitory error constants
_None = 1000
_ObjNotInit = 1001
_SCLHold = 1002
_i2cSDAFault = 1003
_i2cSCLFault = 1004
_i2cStopFault = 1005
HMC6352_READ = $43 ' HMC6352 read address
HMC6352_WRITE = $42 ' HMC6352 write address
WRITE_EEPROM = $77 ' Read a byte from EEPROM
READ_EEPROM = $72 ' Write a byte to EEPROM
WRITE_RAM = $47 ' Read a byte from RAM
READ_RAM = $67 ' Write a byte to RAM
ENTER_SLEEP = $53 ' Enter sleep mode
WAKE_UP = $57 ' Exit sleep mode
MODE_ADDRESS = $4E ' RAM/EEPROM address for operational mode
UPDATE = $4F '
START_CALIB = $43 ' Start the calibration routine
END_CALIB = $45 ' Finish the calibration routine
SAVE_MODE = $4C ' Write the operational mode from RAM to EEPROM
GET_HEADING = $41 ' Read in the calculated heading
READ_WRITE_DELAY = 5_600 ' 70 µs
ENTER_SLEEP_DELAY = 800 ' 10 µs
WAKE_UP_DELAY = 8_000
UPDATE_DELAY = 480_000 ' 6 ms
START_CALIB_DELAY = 800 ' 10 µs
END_CALIB_DELAY = 1_120_000 ' 14 ms
SAVE_MODE_DELAY = 1_000 ' 125 µs
GET_HEADING_DELAY = 480_000 ' 6 ms
RATE = 2000
MotionPlus_Addr_1 = $A6 'for initialization
MotionPlus_Addr_2 = $A4 'after initialized, goes to $A4
MP_CAL_X = 0'7976
MP_CAL_Y = 0'8180
MP_CAL_Z = 0'8259
NUN_CAL_X = 0'515
NUN_CAL_Y = 0'490
NUN_CAL_Z = 0'525
NUN_CAL_JOY_X = 0'128
NUN_CAL_JOY_Y = 0'128
CLASSIC_CAL_L_JOY_X = 0'32
CLASSIC_CAL_L_JOY_Y = 0'32
CLASSIC_CAL_R_JOY_X = 0'32
CLASSIC_CAL_R_JOY_Y = 0'32
MP_ONLY = 4 'MotionPlus only
MP_NUN = 5 'MotionPlus w/ Nunchuck
MP_CLASSIC = 7 'MotionPlus w/ Classic Controller
OBJ
'i2cObject : "i2cObject"
'fMath : "Float32Full"
VAR
long rot_x, rot_y, rot_z
long accel_x, accel_y, accel_z
byte spd_x, spd_y, spd_z
byte data[6]
byte extension, acc_mode
long ErrorState, i2cStarted, lastackbit, CLKScale, _220uS
byte i2cSCL, i2cSDA, SDA, SCL, driveLines
'long joyL_x, joyL_y, joyR_x, joyR_y
'long joy_x, joy_y
'byte button_c, button_z
'byte shoulder_L, shoulder_R
'word digital
PUB Init(_scl, _sda, mode)
i2cSCL := _scl
i2cSDA := _sda
acc_mode := mode
CLKScale := clkfreq / 100_000
_220uS := clkfreq / 100_000 * 22
'fMath.start
Starti2c(i2cSDA, i2cSCL, false)
PUB Digital_Compass
i2cStart
i2cWrite(HMC6352_WRITE, 8)
i2cWrite(GET_HEADING, 8)
i2cStop
i2cStart
i2cWrite(HMC6352_READ, 8)
waitcnt(GET_HEADING_DELAY * CLKScale / 800 + cnt)
result := i2cRead(0) << 8
result |= i2cRead(1)
i2cStop
PUB enable
''enables Motion Plus controller (writing $04 to Address $FE in the $A6 address space)
''this must be done once, which will remap the Motion Plus to the standard extension $A4 address space
writeLocation(MotionPlus_Addr_1, $FE, acc_mode, 8, 8)
waitcnt(_220uS+cnt)
PUB readData
''reads Motion Plus gyro data into memory
writeLocation(MotionPlus_Addr_2, $40, $00, 8, 8)
waitcnt(_220uS+cnt)
i2cStart
i2cWrite(MotionPlus_Addr_2, 8)
i2cWrite(0,8)
i2cStop
waitcnt(_220uS+cnt)
i2cStart
i2cWrite(MotionPlus_Addr_2|1, 8)
data[0] := i2cRead(0)
data[1] := i2cRead(0)
data[2] := i2cRead(0)
data[3] := i2cRead(0)
data[4] := i2cRead(0)
data[5] := i2cRead(1)
i2cStop
if data[5]&3==%10 'MotionPlus data
rot_x := (((data[5]&$FC)<<6)|data[2]) 'pitch
rot_y := (((data[4]&$FC)<<6)|data[1]) 'roll
rot_z := (((data[3]&$FC)<<6)|data[0]) 'yaw
spd_x := data[3]&1
spd_y := (data[4]&2)>>1
spd_z := (data[3]&2)>>1
extension := data[4]&1
elseif data[5]&3==%00 'Extension data
if acc_mode==MP_NUN 'Configured for Nunchuck (removes LSBit of accelerometer readings)
'joy_x := data[0]-NUN_CAL_JOY_X
'joy_y := data[1]-NUN_CAL_JOY_Y
accel_x := ((data[2]<<2)|((data[5]>>3)&2))
accel_y := ((data[3]<<2)|((data[5]>>4)&2))
accel_z := (((data[4]&$FE)<<2)|((data[5]>>5)&6))
' button_z := ((data[5]>>2)&1)^1
' button_c := ((data[5]>>3)&1)^1
{ elseif acc_mode==MP_CLASSIC 'Configured for Classic Controller (removes LSBit of left analog stick readings)
joyL_x := (data[0]&$3E)-CLASSIC_CAL_L_JOY_X '6 bits (consistent w/ dedicated Classic Controller data, Motion Plus removes LSBit)
joyL_y := (data[1]&$3E)-CLASSIC_CAL_L_JOY_Y '6 bits (consistent w/ dedicated Classic Controller data, Motion Plus removes LSBit)
joyR_x := (((data[0]>>2)&($30))|((data[1]>>4)&($0c))|((data[2]>>6)&2))-CLASSIC_CAL_R_JOY_X 'RX only 5 bits, make 6 bits to be consistent w/ LX
joyR_y := ((data[2]<<1)&$3E)-CLASSIC_CAL_R_JOY_Y 'RY only 5 bits, make 6 bits to be consistent w/ LY
shoulder_L := ((data[2]>>2)&($18))|(data[3]>>5) '5 bits
shoulder_R := data[3]&$1F '5 bits
digital := !((1<<15)|(data[5]<<7)|(data[4]>>1)|((data[0]&1)<<7)|((data[1]&1)<<8)) 'invert to 0=not pressed, 1=pressed }
PUB speed_x
return spd_x
PUB rotate_x
''returns pitch rotation data
return rot_x
'if spd_x
' return rot_x/20
'else
'return rot_x/4
PUB rotate_y
''returns roll rotation data
return rot_y
'if spd_y
'return rot_y/20
'else
'return rot_y/4
PUB rotate_z
''returns yaw rotation data
return rot_z
' if spd_z
' return rot_z/20
' else
'return rot_z/4
PUB ext
''returns whether an extension is connected to the MotionPlus or not
return extension
PUB accelX
''returns x axis accelerometer data
if acc_mode==MP_NUN
return accel_x
else
return 0
PUB accelY
''returns y axis accelerometer data
if acc_mode==MP_NUN
return accel_y
else
return 0
PUB accelZ
''returns z axis accelerometer data
if acc_mode==MP_NUN
return accel_z
else
return 0
PUB radius
''radius, used for determining pitch
return ^^(accel_x*accel_x + accel_y*accel_y + accel_z*accel_z)
PUB getLastAckBit : ackbit
' return the last ack bit
return lastAckBit
PUB Starti2c(_i2cSDA, _i2cSCL, _driveSCLLine): okay
if lookdown(_i2cSDA : 0..31) > 0 and lookdown(_i2cSCL : 0..31) > 0
' init the I2C Object
i2cSDA := _i2cSDA
i2cSCL := _i2cSCL
' init the drive'n parameter for SCL lines
driveLines := _driveSCLLine
' init both i2c lines as inputs.
if driveLines == false
dira[i2cSDA] ~
dira[i2cSCL] ~
else
dira[i2cSDA] ~~
dira[i2cSCL] ~~
' init no error state
ErrorState := _none
i2cStarted := true
else
ErrorState := _ObjNotInit
i2cStarted := false
' return true if init was OK
return i2cStarted
PUB getError : errorCode
' return the error state variable
return(ErrorState)
PUB clearError
' clear the error state variable
ErrorState := _none
PUB isStarted : i2cState
' return the i2cStarted flag (true/false)
return i2cStarted
PUB testBus : errorCode
' put both lines to input
' they should both go high from the resistor pullup's
ErrorState := _none
dira[i2cSDA] ~
dira[i2cSCL] ~
if ina[i2cSDA] <> _pinHigh
ErrorState := _i2cSDAFault
if ina[i2cSCL] <> _pinHigh
ErrorState := _i2cSCLFault
return ErrorState
PUB devicePresent(deviceAddress) : ackbit
' send the deviceAddress and listen for the ACK
ackbit := _i2cNAK
if i2cStarted == true
i2cStart
ackbit := i2cWrite(deviceAddress | 0,8)
i2cStop
if ackbit == _i2cACK
ackbit := true
else
ackbit := false
return ackbit
PUB readLocation(deviceAddress, deviceRegister, addressbits, databits) : i2cData | ackbit
' do a standard i2c address, then read
' read a device's register
ackbit := _i2cACK
if i2cStarted == true
i2cStart
ackbit := (ackbit << 1) | i2cWrite(deviceAddress | 0,8)
' cope with bigger than 8 bit deviceRegisters, i.e. EEPROM's use 16 bit or more
case addressbits
8: ' send a 8 bit deviceRegister. (i2cWrite will shift left 24 bits)
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 24, 0)
16: ' send a 16 bit deviceRegister
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 16, 0)
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 24, 0)
24: ' send a 24 bit deviceRegister
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 8, 0)
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 16, 0)
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 24, 0)
32: ' send a 32 bit deviceRegister
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 0, 0)
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 8, 0)
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 16, 0)
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 24, 0)
other: ' any other value passed!
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 24, 0)
i2cStart
ackbit := (ackbit << 1) | i2cWrite(deviceAddress | 1, 8)
i2cData := i2cRead(_i2cNAK)
i2cStop
else
ackbit := _i2cNAK
' set the last i2cACK bit
lastackbit := ackbit
' return the data
return i2cData
PUB writeLocation(deviceAddress, deviceRegister, i2cDataValue, addressbits,databits) : ackbit
' do a standard i2c address, then write
' return the ACK/NAK bit from the device address
ackbit := _i2cACK
if i2cStarted == true
i2cstart
ackbit := (ackbit << 1) | i2cWrite(deviceAddress | 0,8)
' cope with bigger than 8 bit deviceRegisters, i.e. EEPROM's use 16 bit or more
case addressbits
8: ' send a 8 bit deviceRegister
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 24, 0)
16: ' send a 16 bit deviceRegister
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 16, 0)
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 24, 0)
24: ' send a 24 bit deviceRegister
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 8, 0)
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 16, 0)
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 24, 0)
32: ' send a 32 bit deviceRegister
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 0, 0)
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 8, 0)
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 16, 0)
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 24, 0)
other: ' any other value passed!
ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 24, 0)
ackbit := (ackbit << 1) | i2cWrite(i2cDataValue,8)
i2cStop
else
ackbit := _i2cNAK
return ackbit
' ******************************************************************************
' * These are the low level routines *
' ******************************************************************************
PUB i2cStop
' i2c stop sequence - the SDA goes LOW to HIGH while SCL is HIGH
dira[i2cSCL] ~
dira[i2cSDA] ~
PUB i2cStart
' i2c Start sequence - the SDA goes HIGH to LOW while SCL is HIGH
if i2cStarted == true
if driveLines == false
' if the SDA and SCL lines are correctly pulled up to VDD
dira[i2cSDA] ~
dira[i2cSCL] ~
dira[i2cSDA] ~~
outa[i2cSDA] := _pinLow
repeat until ina[i2cSCL] == _pinHigh
else
' if the SDA and SCL lines are left floating
dira[i2cSDA] ~
dira[i2cSCL] ~~
outa[i2cSCL] := _pinHigh
outa[i2cSDA] := _pinHigh
outa[i2cSDA] := _pinLow
PUB i2cWrite(i2cData, i2cBits) : ackbit
' Write i2c data. Data byte is output MSB first, SDA data line is valid
' only while the SCL line is HIGH
ackbit := _i2cNAK
if i2cStarted == true
' set the i2c lines as outputs
dira[i2cSDA] ~~
dira[i2cSCL] ~~
' init the clock line
outa[i2cSCL] := _PinLow
' send the data
i2cData <<= (32 - i2cbits)
repeat 8
' set the SDA while the SCL is LOW
outa[i2cSDA] := (i2cData <-= 1) & 1
' toggle SCL HIGH
outa[i2cSCL] := _PinHigh
' toogle SCL LOW
outa[i2cSCL] := _PinLow
' setup for ACK - pin to input
dira[i2cSDA] ~
' read in the ACK
outa[i2cSCL] := _PinHigh
ackbit := ina[i2cSDA]
outa[i2cSCL] := _PinLow
' leave the SDA pin LOW
dira[i2cSDA] ~~
outa[i2cSDA] := _PinLow
' return the ackbit
return ackbit
PUB i2cRead(ackbit): i2cData
' Read in i2c data, Data byte is output MSB first, SDA data line is valid
' only while the SCL line is HIGH
if i2cStarted == true
' set the SCL to output and the SDA to input
dira[i2cSCL] ~~
dira[i2cSDA] ~
outa[i2cSCL] := _PinLow
' clock in the byte
i2cData := 0
repeat 8
outa[i2cSCL] := _PinHigh
i2cData := (i2cData << 1) | ina[i2cSDA]
outa[i2cSCL] := _PinLow
' send the ACK or NAK
dira[i2cSDA] ~~
outa[i2cSCL] := _PinHigh
outa[i2cSDA] := ackbit
outa[i2cSCL] := _PinLow
' return the data
return i2cData
{{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}}
{{
F32 - Concise floating point code for the Propeller
Copyright (c) 2010 Jonathan "lonesock" Dummer
Released under the MIT License (see the end of this file for details)
+--------------------------------------------------------------------------+
| IEEE 754 compliant 32-bit floating point math routines for the Propeller |
| Based on Float32 & Float32Full v1.5, by Cam Thompson |
| Modified by Jonathan (lonesock) to try to fit all functionality into |
| a single cog, and speed-up the routines where possible. |
+--------------------------------------------------------------------------+
Features:
* prop resources: 1 cog, 667 longs (BST can remove unused Spin code, and the odds are
good that you won't need all the functionality.)
* faster: _Pack, Add, Sub, Sqr, Sin, Cos, Tan, Float, Exp*, Log*, Pow, Mul, Div
* added funcs: Exp2, Log2 (Spin calling code only, no cog bloat),
FMod, ATan, ATan2, ACos, ASin, Floor, Ceil (from Float32A),
FloatTrunc, FloatRound, UintTrunc (added code to the cog)
* more accurate: ATan, ATan2, ACos, ASin
Still Needed / Desired:
* MORE TESTING!! (hint hint [8^)
* User-defined function mechanism (from Float32A)...does anyone use this?
Changelog:
* Nov 27, 2010 - added dispatch table constants, and the Cmd_ptr & Call_ptr functions
* Nov 19, 2010 - adding comments, adjusted call method (sorry heater!)
* Sept 29, 2010 - fixed some comments, added UintTrunc, only 4 longs free [8^(
* Sept 28, 2010 - added FloatTrunc & FloatRound, converted to all ASCII with CRLF end of line.
* Sept 27, 2010 - faster interpolation, CORDIC faster (uses full table), Tan faster (reuses some cals from Sin in Cos) - 26 longs free
* Sept 24, 2010 - fixed the trig functions (had a bug in my table interpolation code)
* Sept 21, 2010 - faster multiply and divide - 26 longs free
* Sept 20, 2010 - added in all the functionality from Float32Full - 14 longs free
* Sept 15, 2010 - fixed race condition on startup, 111 longs free
* Sept 14, 2010 - fixed Exp*, sped up Exp*, Log*, Pow, Cos and Sin again
* Sept 13, 2010 (PM) - fixed Trunc and Round to now do the right thing for large integers. 83 longs available
* Sept 13, 2010 (AM) - new calling convention. 71 longs available in-Cog
USAGE:
* call start first (starts a new cog)
* use functions as expected
* realize that you are storing the result into a regular long variable type (signed 4-byte integer), _encoded_as_a_float_!
}}
CON
' the list of all the PASM functions' offsets in the dispatch table
offGain = 4 ' each entry is a long...4 bytes
' (can be used to call F32 routines from inside your own PASM code)
offAdd = offGain * 0
offSub = offGain * 1
offMul = offGain * 2
offDiv = offGain * 3
offFloat = offGain * 4
offTruncRound = offGain * 5
offUintTrunc = offGain * 6
offSqr = offGain * 7
offCmp = offGain * 8
offSin = offGain * 9
offCos = offGain * 11
offTan = offGain * 12
offLog2 = offGain * 13
offExp2 = offGain * 14
offPow = offGain * 15
offFrac = offGain * 16
offMod = offGain * 17
offASinCos = offGain * 18
offATan2 = offGain * 19
offCeil = offGain * 20
offFloor = offGain * 21
VAR
long f32_Cmd
byte cog
PUB start
{{
Start start floating point engine in a new cog.
Returns: True (non-zero) if cog started, or False (0) if no cog is available.
}}
stop
f32_Cmd := 0
return cog := cognew(@f32_entry, @f32_Cmd) + 1
PUB stop
{{
Stop floating point engine and release the cog.
}}
if cog
cogstop(cog~ - 1)
PUB Cmd_ptr
{{
return the Hub address of f32_Cmd, so other code can call it directly
}}
return @f32_Cmd
PUB Call_ptr
{{
return the Hub address of the dispatch table, so other code can call it directly
}}
return @cmdCallTable
PUB FAdd(a, b)
{{
Addition: result = a + b
Parameters:
a 32-bit floating point value
b 32-bit floating point value
Returns: 32-bit floating point value
}}
result := cmdFAdd
f32_Cmd := @result
repeat while f32_Cmd
PUB FSub(a, b)
{{
Subtraction: result = a - b
Parameters:
a 32-bit floating point value
b 32-bit floating point value
Returns: 32-bit floating point value
}}
result := cmdFSub
f32_Cmd := @result
repeat while f32_Cmd
PUB FMul(a, b)
{{
Multiplication: result = a * b
Parameters:
a 32-bit floating point value
b 32-bit floating point value
Returns: 32-bit floating point value
}}
result := cmdFMul
f32_Cmd := @result
repeat while f32_Cmd
PUB FDiv(a, b)
{{
Division: result = a / b
Parameters:
a 32-bit floating point value
b 32-bit floating point value
Returns: 32-bit floating point value
}}
result := cmdFDiv
f32_Cmd := @result
repeat while f32_Cmd
PUB FFloat(n)
{{
Convert integer to floating point.
Parameters:
n 32-bit integer value
Returns: 32-bit floating point value
}}
result := cmdFFloat
f32_Cmd := @result
repeat while f32_Cmd
PUB UintTrunc(a)
{{
Convert floating point to unsigned integer (with truncation).
Parameters:
a 32-bit floating point value
Returns: 32-bit unsigned integer value
(negartive values are clamped to 0)
}}
result := cmdUintTrunc
f32_Cmd := @result
repeat while f32_Cmd
PUB FTrunc(a) | b
{{
Convert floating point to integer (with truncation).
Parameters:
a 32-bit floating point value
b flag: 0 signifies truncation
Returns: 32-bit integer value
}}
b := %000
result := cmdFTruncRound
f32_Cmd := @result
repeat while f32_Cmd
PUB FRound(a) | b
{{
Convert floating point to integer (with rounding).
Parameters:
a 32-bit floating point value
b flag: 1 signifies rounding to the nearest integer
Returns: 32-bit integer value
}}
b := %001
result := cmdFTruncRound
f32_Cmd := @result
repeat while f32_Cmd
PUB FloatTrunc(a) | b
{{
Convert floating point to whole number (floating point, with truncation).
Parameters:
a 32-bit floating point value
b flag: 2 signifies floating point truncation
Returns: 32-bit floating point value
}}
b := %010
result := cmdFTruncRound
f32_Cmd := @result
repeat while f32_Cmd
PUB FloatRound(a) | b
{{
Convert floating point to whole number (floating point, with rounding).
Parameters:
a 32-bit floating point value
b flag: 3 signifies floating point rounding to the nearest whole number
Returns: 32-bit floating point value
}}
b := %011
result := cmdFTruncRound
f32_Cmd := @result
repeat while f32_Cmd
PUB FSqr(a)
{{
Square root.
Parameters:
a 32-bit floating point value
Returns: 32-bit floating point value
}}
result := cmdFSqr
f32_Cmd := @result
repeat while f32_Cmd
PUB FCmp(a, b)
{{
Floating point comparison.
Parameters:
a 32-bit floating point value
b 32-bit floating point value
Returns: 32-bit integer value
-1 if a < b
0 if a == b
1 if a > b
}}
result := cmdFCmp
f32_Cmd := @result
repeat while f32_Cmd
PUB Sin(a)
{{
Sine of an angle (radians).
Parameters:
a 32-bit floating point value (angle in radians)
Returns: 32-bit floating point value
}}
result := cmdFSin
f32_Cmd := @result
repeat while f32_Cmd
PUB Cos(a)
{{
Cosine of an angle (radians).
Parameters:
a 32-bit floating point value (angle in radians)
Returns: 32-bit floating point value
}}
result := cmdFCos
f32_Cmd := @result
repeat while f32_Cmd
PUB Tan(a)
{{
Tangent of an angle (radians).
Parameters:
a 32-bit floating point value (angle in radians)
Returns: 32-bit floating point value
}}
result := cmdFTan
f32_Cmd := @result
repeat while f32_Cmd
PUB Log(a) | b
{{
Logarithm, base e.
Parameters:
a 32-bit floating point value
b constant used to convert base 2 to base e
Returns: 32-bit floating point value
}}
b := 1.442695041
result := cmdFLog2
f32_Cmd := @result
repeat while f32_Cmd
PUB Log2(a) | b
{{
Logarithm, base 2.
Parameters:
a 32-bit floating point value
b 0 is a flag to skip the base conversion (skips a multiplication by 1.0)
Returns: 32-bit floating point value
}}
b := 0
result := cmdFLog2
f32_Cmd := @result
repeat while f32_Cmd
PUB Log10(a) | b
{{
Logarithm, base 10.
Parameters:
a 32-bit floating point value
b constant used to convert base 2 to base 10
Returns: 32-bit floating point value
}}
b := 3.321928095
result := cmdFLog2
f32_Cmd := @result
repeat while f32_Cmd
PUB Exp(a) | b
{{
Exponential (e raised to the power a).
Parameters:
a 32-bit floating point value
b constant used to convert base 2 to base e
Returns: 32-bit floating point value
}}
b := 1.442695041
result := cmdFExp2
f32_Cmd := @result
repeat while f32_Cmd
PUB Exp2(a) | b
{{
Exponential (2 raised to the power a).
Parameters:
a 32-bit floating point value
b 0 is a flag to skip the base conversion (skips a division by 1.0)
Returns: 32-bit floating point value
}}
b := 0
result := cmdFExp2
f32_Cmd := @result
repeat while f32_Cmd
PUB Exp10(a) | b
{{
Exponential (10 raised to the power a).
Parameters:
a 32-bit floating point value
b constant used to convert base 2 to base 10
Returns: 32-bit floating point value
}}
b := 3.321928095
result := cmdFExp2
f32_Cmd := @result
repeat while f32_Cmd
PUB Pow(a, b)
{{
Power (a to the power b).
Parameters:
a 32-bit floating point value
b 32-bit floating point value
Returns: 32-bit floating point value
}}
result := cmdFPow
f32_Cmd := @result
repeat while f32_Cmd
PUB Frac(a)
{{
Fraction (returns fractional part of a).
Parameters:
a 32-bit floating point value
Returns: 32-bit floating point value
}}
result := cmdFFrac
f32_Cmd := @result
repeat while f32_Cmd
PUB FNeg(a)
{{
Negate: result = -a.
Parameters:
a 32-bit floating point value
Returns: 32-bit floating point value
}}
return a ^ $8000_0000
PUB FAbs(a)
{{
Absolute Value: result = |a|.
Parameters:
a 32-bit floating point value
Returns: 32-bit floating point value
}}
return a & $7FFF_FFFF
PUB Radians(a) | b
{{
Convert degrees to radians
Parameters:
a 32-bit floating point value (angle in degrees)
b the conversion factor
Returns: 32-bit floating point value (angle in radians)
}}
b := constant(pi / 180.0)
result := cmdFMul
f32_Cmd := @result
repeat while f32_Cmd
PUB Degrees(a) | b
{{
Convert radians to degrees
Parameters:
a 32-bit floating point value (angle in radians)
b the conversion factor
Returns: 32-bit floating point value (angle in degrees)
}}
b := constant(180.0 / pi)
result := cmdFMul
f32_Cmd := @result
repeat while f32_Cmd
PUB FMin(a, b)
{{
Minimum: result = the minimum value a or b.
Parameters:
a 32-bit floating point value
b 32-bit floating point value
Returns: 32-bit floating point value
}}
result := cmdFCmp
f32_Cmd := @result
repeat while f32_Cmd
if result < 0
return a
return b
PUB FMax(a, b)
{{
Maximum: result = the maximum value a or b.
Parameters:
a 32-bit floating point value
b 32-bit floating point value
Returns: 32-bit floating point value
}}
result := cmdFCmp
f32_Cmd := @result
repeat while f32_Cmd
if result < 0
return b
return a
PUB FMod(a, b)
{{
Floating point remainder: result = the remainder of a / b.
Parameters:
a 32-bit floating point value
b 32-bit floating point value
Returns: 32-bit floating point value
}}
result := cmdFMod
f32_Cmd := @result
repeat while f32_Cmd
PUB ASin(a) | b
{{
Arc Sine of a (in radians).
Parameters:
a 32-bit floating point value (|a| must be < 1)
b 1 is a flag signifying return the sine component
Returns: 32-bit floating point value (angle in radians)
}}
b := 1
result := cmdASinCos
f32_Cmd := @result
repeat while f32_Cmd
PUB ACos(a) | b
{{
Arc Cosine of a (in radians).
Parameters:
a 32-bit floating point value (|a| must be < 1)
b 0 is a flag signifying return the cosine component
Returns: 32-bit floating point value (angle in radians)
if |a| > 1, NaN is returned
}}
b := 0
result := cmdASinCos
f32_Cmd := @result
repeat while f32_Cmd
PUB ATan(a) | b
{{
Arc Tangent of a.
Parameters:
a 32-bit floating point value
b atan(a) = atan2(a,1.0)
Returns: 32-bit floating point value (angle in radians)
}}
b := 1.0
result := cmdATan2
f32_Cmd := @result
repeat while f32_Cmd
PUB ATan2(a, b)
{{
Arc Tangent of vector a, b (in radians, no division is performed, so b==0 is legal).
Parameters:
a 32-bit floating point value
b 32-bit floating point value
Returns: 32-bit floating point value (angle in radians)
}}
result := cmdATan2
f32_Cmd := @result
repeat while f32_Cmd
PUB Floor(a)
{{
Calculate the floating point value of the nearest integer <= a.
Parameters:
a 32-bit floating point value
Returns: 32-bit floating point value
}}
result := cmdFloor
f32_Cmd := @result
repeat while f32_Cmd
PUB Ceil(a)
{{
Calculate the floating point value of the nearest integer >= a.
Parameters:
a 32-bit floating point value
Returns: 32-bit floating point value
}}
result := cmdCeil
f32_Cmd := @result
repeat while f32_Cmd
CON
SignFlag = $1
ZeroFlag = $2
NaNFlag = $8
DAT
'----------------------------
' Assembly language routines
'----------------------------
'----------------------------
' Main control loop
'----------------------------
org 0 ' (try to keep 2 or fewer instructions between rd/wrlong)
f32_entry rdlong ret_ptr, par wz ' wait for command to be non-zero, and store it in the call location
if_z jmp #f32_entry
rdlong :execCmd, ret_ptr ' get the pointer to the return value ("@result")
add ret_ptr, #4
rdlong fNumA, ret_ptr ' fnumA is the long after "result"
add ret_ptr, #4
rdlong fNumB, ret_ptr ' fnumB is the long after fnumA
sub ret_ptr, #8
:execCmd nop ' execute command, which was replaced by getCommand
:finishCmd wrlong fnumA, ret_ptr ' store the result (2 longs before fnumB)
mov t1, #0 ' zero out the command register
wrlong t1, par ' clear command status
jmp #f32_entry ' wait for next command
'----------------------------
' addition and subtraction
' fnumA = fnumA +- fnumB
'----------------------------
_FSub xor fnumB, Bit31 ' negate B
jmp #_FAdd ' add values
_FAdd call #_Unpack2 ' unpack two variables
if_c_or_z jmp #_FAdd_ret ' check for NaN or B = 0
test flagA, #SignFlag wz ' negate A mantissa if negative
if_nz neg manA, manA
test flagB, #SignFlag wz ' negate B mantissa if negative
if_nz neg manB, manB
mov t1, expA ' align mantissas
sub t1, expB
abs t1, t1 wc
max t1, #31
if_nc sar manB, t1
if_c sar manA, t1
if_c mov expA, expB
add manA, manB ' add the two mantissas
abs manA, manA wc ' store the absolte value,
muxc flagA, #SignFlag ' and flag if it was negative
call #_Pack ' pack result and exit
_FSub_ret
_FAdd_ret ret
'----------------------------
' multiplication
' fnumA *= fnumB
'----------------------------
_FMul call #_Unpack2 ' unpack two variables
if_c jmp #_FMul_ret ' check for NaN
xor flagA, flagB ' get sign of result
add expA, expB ' add exponents
' standard method: 404 counts for this block
mov t1, #0 ' t1 is my accumulator
mov t2, #24 ' loop counter for multiply (only do the bits needed...23 + implied 1)
shr manB, #6 ' start by right aligning the B mantissa
:multiply shr t1, #1 ' shift the previous accumulation down by 1
shr manB, #1 wc ' get multiplier bit
if_c add t1, manA ' if the bit was set, add in the multiplicand
djnz t2, #:multiply ' go back for more
mov manA, t1 ' yes, that's my final answer.
call #_Pack
_FMul_ret ret
'----------------------------
' division
' fnumA /= fnumB
'----------------------------
_FDiv call #_Unpack2 ' unpack two variables
if_c_or_z mov fnumA, NaN ' check for NaN or divide by 0
if_c_or_z jmp #_FDiv_ret
xor flagA, flagB ' get sign of result
sub expA, expB ' subtract exponents
' slightly faster division, using 26 passes instead of 30
mov t1, #0 ' clear quotient
mov t2, #26 ' loop counter for divide (need 24, plus 2 for rounding)
:divide ' divide the mantissas
cmpsub manA, manB wc
rcl t1, #1
shl manA, #1
djnz t2, #:divide
shl t1, #4 ' align the result (we did 26 instead of 30 iterations)
mov manA, t1 ' get result and exit
call #_Pack
_FDiv_ret ret
'------------------------------------------------------------------------------
' fnumA = float(fnumA)
'------------------------------------------------------------------------------
_FFloat abs manA, fnumA wc,wz ' get |integer value|
if_z jmp #_FFloat_ret ' if zero, exit
mov flagA, #0 ' set the sign flag
muxc flagA, #SignFlag ' depending on the integer's sign
mov expA, #29 ' set my exponent
call #_Pack ' pack and exit
_FFloat_ret ret
'------------------------------------------------------------------------------
' rounding and truncation
' fnumB controls the output format:
' %00 = integer, truncate
' %01 = integer, round
' %10 = float, truncate
' %11 = float, round
'------------------------------------------------------------------------------
_FTruncRound mov t1, fnumA ' grab a copy of the input
call #_Unpack ' unpack floating point value
' Are we going for float or integer?
cmpsub fnumB, #%10 wc ' clear bit 1 and set the C flag if it was a 1
rcl t2, #1
and t2, #1 wz ' Z now signified integer output
shl manA, #2 ' left justify mantissa
sub expA, #30 ' our target exponent is 30
abs expA, expA wc ' adjust for exponent sign, and track if it was negative
if_z_and_nc mov manA, NaN ' integer output, and it's too large for us to handle
if_z_and_nc jmp #:check_sign
if_nz_and_nc mov fnumA, t1 ' float output, and we're already all integer
if_nz_and_nc jmp #_FTruncRound_ret
' well, I need to kill off some bits, so let's do it
max expA, #31 wc
shr manA, expA
if_c add manA, fnumB ' round up 1/2 lsb if desired, and if it isn't supposed to be 0! (if expA was > 31)
shr manA, #1
if_z jmp #:check_sign ' integer output?
mov expA, #29
call #_Pack
jmp #_FTruncRound_ret
:check_sign test flagA, #signFlag wz ' check sign and exit
negnz fnumA, manA
_FTruncRound_ret ret
'------------------------------------------------------------------------------
' truncation to unsigned integer
' fnumA = unsigned int(fnumA), clamped to 0
'------------------------------------------------------------------------------
_UintTrunc call #_Unpack
mov fnumA, #0
test flagA, #SignFlag wc
if_c_or_z jmp #_UintTrunc_ret ' if the input number was negative or zero, we're done
shl manA, #2 ' left justify mantissa
sub expA, #31 ' our target exponent is 31
abs expA, expA wc,wz
if_a neg fnumA, #1 ' if we needed to shift left, we're already maxed out
if_be cmp expA, #32 wc ' otherwise, if we need to shift right by more than 31, the answer is 0
if_c shr manA, expA ' OK, shift it down
if_c mov fnumA, manA
_UintTrunc_ret ret
'------------------------------------------------------------------------------
' square root
' fnumA = sqrt(fnumA)
'------------------------------------------------------------------------------
_FSqr call #_Unpack ' unpack floating point value
if_c_or_z jmp #_FSqr_ret ' check for NaN or zero
test flagA, #signFlag wz ' check for negative
if_nz mov fnumA, NaN ' yes, then return NaN
if_nz jmp #_FSqr_ret
sar expA, #1 wc ' if odd exponent, shift mantissa
if_c shl manA, #1
add expA, #1
mov t2, #29
mov fnumA, #0 ' set initial result to zero
:sqrt ' what is the delta root^2 if we add in this bit?
mov t3, fnumA
shl t3, #2
add t3, #1
shl t3, t2
' is the remainder >= delta?
cmpsub manA, t3 wc
rcl fnumA, #1
shl manA, #1
djnz t2, #:sqrt
mov manA, fnumA ' store new mantissa value and exit
call #_Pack
_FSqr_ret ret
'------------------------------------------------------------------------------
' compare fnumA , fnumB
' fnumA =
' 1 if fnumA > fnumB
' -1 if fnumA < fnumB
' 0 if fnumA = fnumB
'------------------------------------------------------------------------------
_FCmp mov t1, fnumA ' compare signs
xor t1, fnumB
and t1, Bit31 wz
if_z jmp #:cmp1 ' same, then compare magnitude
mov t1, fnumA ' check for +0 or -0
or t1, fnumB
andn t1, Bit31 wz,wc
if_z jmp #:exit
test fnumA, Bit31 wc ' compare signs
jmp #:exit
:cmp1 test fnumA, Bit31 wz ' check signs
if_nz jmp #:cmp2
cmp fnumA, fnumB wz,wc
jmp #:exit
:cmp2 cmp fnumB, fnumA wz,wc ' reverse test if negative
:exit mov fnumA, #1 ' if fnumA > fnumB, t1 = 1
if_c neg fnumA, fnumA ' if fnumA < fnumB, t1 = -1
if_z mov fnumA, #0 ' if fnumA = fnumB, t1 = 0
_FCmp_ret ret
'------------------------------------------------------------------------------
' new table lookup code
' Inputs
' t1 = 31-bit number: 1-bit 0, then 11-bits real, then 20-bits fraction (allows the sine table to use the top bit)
' t2 = table base address
' Outputs
' t1 = 30-bit interpolated number
'------------------------------------------------------------------------------
_Table_Interp ' store the fractional part
mov t4, t1 ' will store reversed so a SAR will shift the value and get a bit
rev t4, #12 ' ignore the top 12 bits, and reverse the rest
' align the input number to get the table offset, multiplied by 2
shr t1, #19
add t2, t1
' read the 2 intermediate values, and scale them for interpolation
rdword t1, t2
shl t1, #14
add t2, #2
rdword t2, t2
shl t2, #14
' interpolate
sub t2, t1 ' change from 2 points to delta
movs t2, t4 ' make the low 9 bits the multiplier (reversed)
mov t3, #9 ' do 9 steps
:interp sar t2, #1 wc ' divide the delta by 2, and get the MSB multiplier bit
if_c add t1, t2 ' if the multiplier bit was 1, add in the shifter delta
djnz t3, #:interp ' keep going, 9 times around
' done, and the answer is in t1, bit 29 aligned
_Table_Interp_ret ret
'------------------------------------------------------------------------------
' sine and cosine
' fnumA = sin(fnumA) for sine
' fnumA = sin(fnumA+pi/2) for cosine
' note: resume tan allows reuse of the angle scaling when calling
' both sine and cosine of the same angle.
'------------------------------------------------------------------------------
OneOver2Pi long 1.0 / (2.0 * pi) ' I need this constant to get the fractional angle
_Cos mov t4, bit29 ' adjust sine to cosine at the last possible minute by adding 90 degrees
andn fnumA, bit31 ' nuke the sign bit
jmp #_SinCos_cont
_Sin mov t4, #0 ' just sine, and keep my sign bit
_SinCos_cont mov fnumB, OneOver2Pi
call #_FMul ' rescale angle from [0..2pi] to [0..1]
' now, work with the raw value
call #_Unpack
' get the whole and fractional bits
add expA, #2 ' bias the exponent by 3 so the resulting data will be 31-bit aligned
abs expA, expA wc ' was the exponent positive or negative?
max expA, #31 ' limit to 31, otherwise we do weird wrapping things
if_c shr manA, expA ' -exp: shift right to bring down to 1.0
if_nc shl manA, expA ' +exp: shift left to throw away the high bits
mov t6, manA ' store the address in case Tan needs it
add manA, t4 ' adjust for cosine?
_resume_Tan test manA, bit29 wz
negnz t1, manA
shl t1, #2
mov t2, SineTable
call #_Table_Interp
' rebuild the number
test manA, bit30 wz ' check if we're in quadrant 3 or 4
abs manA, t1 ' move my number into the mantissa
shr manA, #16 ' but the table went to $FFFF, so scale up a bit to
addabs manA, t1 ' get to &10000
if_nz xor flagA, #SignFlag ' invert my sign bit, if the mantissa would have been negative (quad 3 or 4)
neg expA, #1 ' exponent is -1
call #_Pack
_resume_Tan_ret
_Cos_ret
_Sin_ret ret
'------------------------------------------------------------------------------
' tangent
' fnumA = tan(fnumA) = sin(fnumA) / cos(fnumA)
'------------------------------------------------------------------------------
_Tan call #_Sin
mov t7, fnumA
' skip the angle normalizing, much faster
mov manA, t6 ' was manA for Sine
add manA, bit29 ' add in 90 degrees
call #_resume_Tan ' go back and recompute the float
mov fnumB, fnumA ' move Cosine into fnumB
mov fnumA, t7 ' move Sine into fnumA
call #_FDiv ' divide
_Tan_ret ret
'------------------------------------------------------------------------------
' log2
' fnumA = log2(fnumA)
' may be divided by fnumB to change bases
'------------------------------------------------------------------------------
_Log2 call #_Unpack ' unpack variable
if_nz_and_nc test flagA, #SignFlag wc ' if NaN or <= 0, return NaN
if_z_or_c jmp #:exitNaN
mov t1, manA
shl t1, #3
shr t1, #1
mov t2, LogTable
call #_Table_Interp
' store the interpolated table lookup
mov manA, t1
shr manA, #5 ' clear the top 7 bits (already 2 free
' process the exponent
abs expA, expA wc
muxc flagA, #SignFlag
' recombine exponent into the mantissa
shl expA, #25
negc manA, manA
add manA, expA
mov expA, #4
' make it a floating point number
call #_Pack
' convert the base
cmp fnumB, #0 wz ' check that my divisor isn't 0 (which flags that we're doing log2)
if_nz call #_FDiv ' convert the base (unless fnumB was 0)
jmp #_Log2_ret
:exitNaN mov fnumA, NaN ' return NaN
_Log2_ret ret
'------------------------------------------------------------------------------
' exp2
' fnumA = 2 ** fnumA
' may be multiplied by fnumB to change bases
'------------------------------------------------------------------------------
' 1st off, convert the base
_Exp2 cmp fnumB, #0 wz
if_nz call #_FMul
call #_Unpack
shl manA, #2 ' left justify mantissa
mov t1, expA ' copy the local exponent
' OK, get the whole number
sub t1, #30 ' our target exponent is 31
abs expA, t1 wc ' adjust for exponent sign, and track if it was negative
if_c jmp #:cont_Exp2
' handle this case depending on the sign
test flagA, #signFlag wz
if_z mov fnumA, NaN ' nope, was positive, bail with NaN (happens to be the largest positive integer)
if_nz mov fnumA, #0
jmp #_Exp2_ret
:cont_Exp2 mov t2, manA
max expA, #31
shr t2, expA
shr t2, #1
mov expA, t2
' get the fractional part
add t1, #31
abs t2, t1 wc
if_c shr manA, t2
if_nc shl manA, t2
' do the table lookup
mov t1, manA
shr t1, #1
mov t2, ALogTable
call #_Table_Interp
' store a copy of the sign
mov t6, flagA
' combine
mov manA, t1
or manA, bit30
sub expA, #1
mov flagA, #0
call #_Pack
test t6, #signFlag wz ' check sign and store this back in the exponent
if_z jmp #_Exp2_ret
mov fnumB, fnumA ' yes, then invert
mov fnumA, One
call #_FDiv
_Exp2_ret ret
'------------------------------------------------------------------------------
' power
' fnumA = fnumA raised to power fnumB
'------------------------------------------------------------------------------
_Pow mov t7, fnumA wc ' save sign of result
if_nc jmp #:pow3 ' check if negative base
mov fnumA, fnumB ' check exponent
call #_Unpack
mov fnumA, t7 ' restore base
if_z jmp #:pow2 ' check for exponent = 0
test expA, Bit31 wz ' if exponent < 0, return NaN
if_nz jmp #:pow1
max expA, #23 ' check if exponent = integer
shl manA, expA
and manA, Mask29 wz, nr
if_z jmp #:pow2 ' yes, then check if odd
:pow1 mov fnumA, NaN ' return NaN
jmp #_Pow_ret
:pow2 test manA, Bit29 wz ' if odd, then negate result
if_z andn t7, Bit31
:pow3 andn fnumA, Bit31 ' get |fnumA|
mov t6, fnumB ' save power
call #_Log2 ' get log of base
mov fnumB, t6 ' multiply by power
call #_FMul
call #_Exp2 ' get result
test t7, Bit31 wz ' check for negative
if_nz xor fnumA, Bit31
_Pow_ret ret
'------------------------------------------------------------------------------
' fraction
' fnumA = fractional part of fnumA
'------------------------------------------------------------------------------
_Frac call #_Unpack ' get fraction
test expA, Bit31 wz ' check for exp < 0 or NaN
if_c_or_nz jmp #:exit
max expA, #23 ' remove the integer
shl manA, expA
and manA, Mask29
mov expA, #0 ' return fraction
:exit call #_Pack
andn fnumA, Bit31
_Frac_ret ret
'------------------------------------------------------------------------------
' input: fnumA 32-bit floating point value
' fnumB 32-bit floating point value
' output: flagA fnumA flag bits (Nan, Infinity, Zero, Sign)
' expA fnumA exponent (no bias)
' manA fnumA mantissa (aligned to bit 29)
' flagB fnumB flag bits (Nan, Infinity, Zero, Sign)
' expB fnumB exponent (no bias)
' manB fnumB mantissa (aligned to bit 29)
' C flag set if fnumA or fnumB is NaN
' Z flag set if fnumB is zero
' changes: fnumA, flagA, expA, manA, fnumB, flagB, expB, manB, t1
'------------------------------------------------------------------------------
_Unpack2 mov t1, fnumA ' save A
mov fnumA, fnumB ' unpack B to A
call #_Unpack
if_c jmp #_Unpack2_ret ' check for NaN
mov fnumB, fnumA ' save B variables
mov flagB, flagA
mov expB, expA
mov manB, manA
mov fnumA, t1 ' unpack A
call #_Unpack
cmp manB, #0 wz ' set Z flag
_Unpack2_ret ret
'------------------------------------------------------------------------------
' input: fnumA 32-bit floating point value
' output: flagA fnumA flag bits (Nan, Infinity, Zero, Sign)
' expA fnumA exponent (no bias)
' manA fnumA mantissa (aligned to bit 29)
' C flag set if fnumA is NaN
' Z flag set if fnumA is zero
' changes: fnumA, flagA, expA, manA
'------------------------------------------------------------------------------
_Unpack mov flagA, fnumA ' get sign
shr flagA, #31
mov manA, fnumA ' get mantissa
and manA, Mask23
mov expA, fnumA ' get exponent
shl expA, #1
shr expA, #24 wz
if_z jmp #:zeroSubnormal ' check for zero or subnormal
cmp expA, #255 wz ' check if finite
if_nz jmp #:finite
mov fnumA, NaN ' no, then return NaN
mov flagA, #NaNFlag
jmp #:exit2
:zeroSubnormal or manA, expA wz,nr ' check for zero
if_nz jmp #:subnorm
or flagA, #ZeroFlag ' yes, then set zero flag
neg expA, #150 ' set exponent and exit
jmp #:exit2
:subnorm shl manA, #7 ' fix justification for subnormals
:subnorm2 test manA, Bit29 wz
if_nz jmp #:exit1
shl manA, #1
sub expA, #1
jmp #:subnorm2
:finite shl manA, #6 ' justify mantissa to bit 29
or manA, Bit29 ' add leading one bit
:exit1 sub expA, #127 ' remove bias from exponent
:exit2 test flagA, #NaNFlag wc ' set C flag
cmp manA, #0 wz ' set Z flag
_Unpack_ret ret
'------------------------------------------------------------------------------
' input: flagA fnumA flag bits (Nan, Infinity, Zero, Sign)
' expA fnumA exponent (no bias)
' manA fnumA mantissa (aligned to bit 29)
' output: fnumA 32-bit floating point value
' changes: fnumA, flagA, expA, manA
'------------------------------------------------------------------------------
_Pack cmp manA, #0 wz ' check for zero
if_z mov expA, #0
if_z jmp #:exit1
sub expA, #380 ' take us out of the danger range for djnz
:normalize shl manA, #1 wc ' normalize the mantissa
if_nc djnz expA, #:normalize ' adjust exponent and jump
add manA, #$100 wc ' round up by 1/2 lsb
addx expA, #(380 + 127 + 2) ' add bias to exponent, account for rounding (in flag C, above)
mins expA, Minus23
maxs expA, #255
abs expA, expA wc,wz ' check for subnormals, and get the abs in case it is
if_a jmp #:exit1
:subnormal or manA, #1 ' adjust mantissa
ror manA, #1
shr manA, expA
mov expA, #0 ' biased exponent = 0
:exit1 mov fnumA, manA ' bits 22:0 mantissa
shr fnumA, #9
movi fnumA, expA ' bits 23:30 exponent
shl flagA, #31
or fnumA, flagA ' bit 31 sign
_Pack_ret ret
'------------------------------------------------------------------------------
' modulo
' fnumA = fnumA mod fnumB
'------------------------------------------------------------------------------
_FMod mov t4, fnumA ' save fnumA
mov t5, fnumB ' save fnumB
call #_FDiv ' a - float(fix(a/b)) * b
mov fnumB, #0
call #_FTruncRound
call #_FFloat
mov fnumB, t5
call #_FMul
or fnumA, Bit31
mov fnumB, t4
andn fnumB, Bit31
call #_FAdd
test t4, Bit31 wz ' if a < 0, set sign
if_nz or fnumA, Bit31
_FMod_ret ret
'------------------------------------------------------------------------------
' arctan2
' fnumA = atan2( fnumA, fnumB )
' note: y = fnumA, x = fnumB: same as C++, opposite of Excel!
'------------------------------------------------------------------------------
_ATan2 call #_Unpack2 ' OK, start with the basics
mov fnumA, #0 ' clear my accumulator
' which is the larger exponent?
sub expA, expB
abs expA, expA wc
' make the exponents equal
if_c shr manA, expA
if_nc shr manB, expA
' correct signs based on the Quadrant
test flagA, #SignFlag wc
test flagB, #SignFlag wz
if_z_eq_c neg manA, manA
if_nz sumc fnumA, CORDIC_Pi
' do the CORDIC thing
mov t1, #0
mov t2, #25 ' 20 gets you the same error range as the original, 29 is best, 25 is a nice compromise
movs :load_C_table, #CORDIC_Angles
:CORDIC ' do the actual CORDIC thing
mov t3, manA wc ' mark whether our Y component is negative or not
sar t3, t1
mov t4, manB
sar t4, t1
sumc manB, t3 ' C determines the direction of the rotation
sumnc manA, t4 wz ' (be ready to short-circuit as soon as the Y component is 0)
:load_C_table sumc fnumA, 0-0
' update all my counters (including the code ones)
add :load_C_table, #1
add t1, #1
' go back for more?
djnz t2, #:CORDIC
' convert to a float
mov expA, #1
abs manA, fnumA wc
muxc flagA, #SignFlag
call #_Pack
_ATan2_ret ret
CORDIC_Pi long $3243f6a8 ' Pi in 30 bits (otherwise we can overflow)
' The CORDIC angle table...binary 30-bit representation of atan(2^-i)
CORDIC_Angles long $c90fdaa, $76b19c1, $3eb6ebf, $1fd5ba9, $ffaadd
long $7ff556, $3ffeaa, $1fffd5, $ffffa, $7ffff
long $3ffff, $20000, $10000, $8000, $4000
long $2000, $1000, $800, $400, $200
long $100, $80, $40, $20, $10
'long $8, $4, $2, $1
'------------------------------------------------------------------------------
' arcsine or arccosine
' fnumA = asin or acos(fnumA)
' asin( x ) = atan2( x, sqrt( 1 - x*x ) )
' acos( x ) = atan2( sqrt( 1 - x*x ), x )
'------------------------------------------------------------------------------
_ASinCos ' grab a copy of both operands
mov t5, fnumA
mov t6, fnumB
' square fnumA
mov fnumB, fnumA
call #_FMul
mov fnumB, fnumA
mov fnumA, One
call #_FSub
' quick error check
test fnumA, bit31 wc
if_c mov fnumA, NaN
if_c jmp #_ASinCos_ret
' carry on
call #_FSqr
' check if this is sine or cosine (determines which goes into fnumA and fnumB)
mov t6, t6 wz
if_z mov fnumB, t5
if_nz mov fnumB, fnumA
if_nz mov fnumA, t5
call #_ATan2
_ASinCos_ret ret
'------------------------------------------------------------------------------
' _Floor fnumA = floor(fnumA)
' _Ceil fnumA = ceil(fnumA)
'------------------------------------------------------------------------------
_Ceil mov t6, #1 ' set adjustment value
jmp #floor2
_Floor neg t6, #1 ' set adjustment value
floor2 call #_Unpack ' unpack variable
if_c jmp #_Floor_ret ' check for NaN
cmps expA, #23 wc, wz ' check for no fraction
if_nc jmp #_Floor_ret
mov t4, fnumA ' get integer value
mov fnumB, #0
call #_FTruncRound
mov t5, fnumA
xor fnumA, t6
test fnumA, Bit31 wz
if_nz jmp #:exit
mov fnumA, t4 ' get fraction
call #_Frac
or fnumA, fnumA wz
if_nz add t5, t6 ' if non-zero, then adjust
:exit mov fnumA, t5 ' convert integer to float
call #_FFloat '}
_Ceil_ret
_Floor_ret ret
'-------------------- constant values -----------------------------------------
One long 1.0
NaN long $7FFF_FFFF
Minus23 long -23
Mask23 long $007F_FFFF
Mask29 long $1FFF_FFFF
Bit29 long $2000_0000
Bit30 long $4000_0000
Bit31 long $8000_0000
LogTable long $C000
ALogTable long $D000
SineTable long $E000
'-------------------- initialized variables -----------------------------------
'-------------------- local variables -----------------------------------------
ret_ptr res 1
t1 res 1
t2 res 1
t3 res 1
t4 res 1
t5 res 1
t6 res 1
t7 res 1
t8 res 1
fnumA res 1 ' floating point A value
flagA res 1
expA res 1
manA res 1
fnumB res 1 ' floating point B value
flagB res 1
expB res 1
manB res 1
fit $1F0 ' A cog has 496 longs available, the last 16 (to make it up to 512) are register shadows.
' command dispatch table: must be compiled along with PASM code in
' Cog RAM to know the addresses, but does not neet to fit in it.
cmdCallTable
cmdFAdd call #_FAdd
cmdFSub call #_FSub
cmdFMul call #_FMul
cmdFDiv call #_FDiv
cmdFFloat call #_FFloat
cmdFTruncRound call #_FTruncRound
cmdUintTrunc call #_UintTrunc
cmdFSqr call #_FSqr
cmdFCmp call #_FCmp
cmdFSin call #_Sin
cmdFCos call #_Cos
cmdFTan call #_Tan
cmdFLog2 call #_Log2
cmdFExp2 call #_Exp2
cmdFPow call #_Pow
cmdFFrac call #_Frac
cmdFMod call #_FMod
cmdASinCos call #_ASinCos
cmdATan2 call #_ATan2
cmdCeil call #_Ceil
cmdFloor call #_Floor
{{
+------------------------------------------------------------------------------------------------------------------------------+
| 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. |
+------------------------------------------------------------------------------------------------------------------------------+
}}
{{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}}
CON
_CLKMODE = XTAL1 + PLL16X
_XINFREQ = 5_000_000
''Resolution
Resolution = 8150 '<-- 8.15us
''Commands
UpdateTonToff = 1
IO_State = 2
DutyOverRide = 3
SyncPhase = 4
''DutyModes
Duty_default = 0 '<-- Can be anything other than 1 or 2
Duty_100 = 1
Duty_0 = 2
''StateModes
DisablePin = 0
EnablePin = 1
VAR
long cog, command, ARG0, ARG1, ARG2
PUB Stop
cogstop(cog)
PUB Start '' Initialize PWM cog
cog := cognew(@PWM_Asm, @command)
PUB Servo(Pin,PulseWidth)|Ton,Toff '' Pin = 0 to 31
If PulseWidth <> 0 '' PulseWidth ... 1000 = 1ms / 2000 = 2ms
Ton := (PulseWidth * 1000)/Resolution '' If Pulsewidth = 0 then the pin is disabled
Toff := ((20000-PulseWidth)*1000)/Resolution
PWM(Pin,Ton ,Toff)
else
StateMode(Pin,0)
PUB PWM(Pin,OnTime,OffTime) '' Pin = 0 to 31
If OnTime ==0 and OffTime == 0
StateMode(Pin,0)
If OnTime == 0 '' An OnTime value of 0 forces a 0% Duty Cycle
DutyMode(Pin,2) 'error fix; changed to 2
StateMode(Pin,1) 'bug fix; added to prevent small error when OnTime starts out as Zero
If OffTime == 0 '' An OffTime value of 0 forces a 100% Duty Cycle
DutyMode(Pin,1) 'error fix; changed to 1
StateMode(Pin,1) 'bug fix; added to prevent small error when OnTime starts out as Zero
If OnTime ==0 and OffTime == 0 '' If OnTime & OffTime are both 0 then the pin is
StateMode(Pin,0) '' disabled.
If OnTime <>0 and OffTime <>0 '' ONLY allow OnTime and OffTime to update if they are NOT Zero
ARG0 := Pin
ARG1 := OnTime - 1
ARG2 := OffTime - 1
command := UpdateTonToff
repeat until command == 0
StateMode(Pin,1)
DutyMode(Pin,0) 'bug fix; added to make sure Default Duty cycle is selected
PUB StateMode(Pin,State) '' Pin = 0 to 31
ARG0 := Pin '' If State is 0 then pin is disabled
ARG1 := State '' If State is 1 then pin is enabled
command := IO_State
repeat until command == 0
PUB DutyMode(Pin,Mode) '' Pin = 0 to 31
ARG0 := Pin '' If Duty = 0 then the pin is in it's default state
ARG1 := Mode '' If Duty = 1 then the pin is forced HIGH ; 100%
command := DutyOverRide '' If Duty = 2 then the pin is forced LOW ; 0%
repeat until command == 0
DAT
PWM_Asm org
mov temp, par
mov cmd_address, temp 'Acquire command variable address
add temp, #4
mov ARG_0, temp 'Acquire ARG_0 variable address
add temp, #4
mov ARG_1, temp 'Acquire ARG_1 variable address
add temp, #4
mov ARG_2, temp 'Acquire ARG_2 variable address
Main_PWM_Loop
'-------------------------------------------------------------
sub t1 + 00, #1 wc
Ch01 if_c xor Ch + 00, Mask + 00 wz
if_c_and_z mov t1 + 00, T_Off_Ch + 00
if_c_and_nz mov t1 + 00, T_On__Ch + 00
'-------------------------------------------------------------
sub t1 + 01, #1 wc
Ch02 if_c xor Ch + 01, Mask + 01 wz
if_c_and_z mov t1 + 01, T_Off_Ch + 01
if_c_and_nz mov t1 + 01, T_On__Ch + 01
'-------------------------------------------------------------
sub t1 + 02, #1 wc
Ch03 if_c xor Ch + 02, Mask + 02 wz
if_c_and_z mov t1 + 02, T_Off_Ch + 02
if_c_and_nz mov t1 + 02, T_On__Ch + 02
'-------------------------------------------------------------
sub t1 + 03, #1 wc
Ch04 if_c xor Ch + 03, Mask + 03 wz
if_c_and_z mov t1 + 03, T_Off_Ch + 03
if_c_and_nz mov t1 + 03, T_On__Ch + 03
'-------------------------------------------------------------
sub t1 + 04, #1 wc
Ch05 if_c xor Ch + 04, Mask + 04 wz
if_c_and_z mov t1 + 04, T_Off_Ch + 04
if_c_and_nz mov t1 + 04, T_On__Ch + 04
'-------------------------------------------------------------
sub t1 + 05, #1 wc
Ch06 if_c xor Ch + 05, Mask + 05 wz
if_c_and_z mov t1 + 05, T_Off_Ch + 05
if_c_and_nz mov t1 + 05, T_On__Ch + 05
'-------------------------------------------------------------
sub t1 + 06, #1 wc
Ch07 if_c xor Ch + 06, Mask + 06 wz
if_c_and_z mov t1 + 06, T_Off_Ch + 06
if_c_and_nz mov t1 + 06, T_On__Ch + 06
'-------------------------------------------------------------
sub t1 + 07, #1 wc
Ch08 if_c xor Ch + 07, Mask + 07 wz
if_c_and_z mov t1 + 07, T_Off_Ch + 07
if_c_and_nz mov t1 + 07, T_On__Ch + 07
'-------------------------------------------------------------
sub t1 + 08, #1 wc
Ch09 if_c xor Ch + 08, Mask + 08 wz
if_c_and_z mov t1 + 08, T_Off_Ch + 08
if_c_and_nz mov t1 + 08, T_On__Ch + 08
'-------------------------------------------------------------
sub t1 + 09, #1 wc
Ch10 if_c xor Ch + 09, Mask + 09 wz
if_c_and_z mov t1 + 09, T_Off_Ch + 09
if_c_and_nz mov t1 + 09, T_On__Ch + 09
'-------------------------------------------------------------
sub t1 + 10, #1 wc
Ch11 if_c xor Ch + 10, Mask + 10 wz
if_c_and_z mov t1 + 10, T_Off_Ch + 10
if_c_and_nz mov t1 + 10, T_On__Ch + 10
'-------------------------------------------------------------
sub t1 + 11, #1 wc
Ch12 if_c xor Ch + 11, Mask + 11 wz
if_c_and_z mov t1 + 11, T_Off_Ch + 11
if_c_and_nz mov t1 + 11, T_On__Ch + 11
'-------------------------------------------------------------
sub t1 + 12, #1 wc
Ch13 if_c xor Ch + 12, Mask + 12 wz
if_c_and_z mov t1 + 12, T_Off_Ch + 12
if_c_and_nz mov t1 + 12, T_On__Ch + 12
'-------------------------------------------------------------
sub t1 + 13, #1 wc
Ch14 if_c xor Ch + 13, Mask + 13 wz
if_c_and_z mov t1 + 13, T_Off_Ch + 13
if_c_and_nz mov t1 + 13, T_On__Ch + 13
'-------------------------------------------------------------
sub t1 + 14, #1 wc
Ch15 if_c xor Ch + 14, Mask + 14 wz
if_c_and_z mov t1 + 14, T_Off_Ch + 14
if_c_and_nz mov t1 + 14, T_On__Ch + 14
'-------------------------------------------------------------
sub t1 + 15, #1 wc
Ch16 if_c xor Ch + 15, Mask + 15 wz
if_c_and_z mov t1 + 15, T_Off_Ch + 15
if_c_and_nz mov t1 + 15, T_On__Ch + 15
'-------------------------------------------------------------
sub t1 + 16, #1 wc
Ch17 if_c xor Ch + 16, Mask + 16 wz
if_c_and_z mov t1 + 16, T_Off_Ch + 16
if_c_and_nz mov t1 + 16, T_On__Ch + 16
'-------------------------------------------------------------
sub t1 + 17, #1 wc
Ch18 if_c xor Ch + 17, Mask + 17 wz
if_c_and_z mov t1 + 17, T_Off_Ch + 17
if_c_and_nz mov t1 + 17, T_On__Ch + 17
'-------------------------------------------------------------
sub t1 + 18, #1 wc
Ch19 if_c xor Ch + 18, Mask + 18 wz
if_c_and_z mov t1 + 18, T_Off_Ch + 18
if_c_and_nz mov t1 + 18, T_On__Ch + 18
'-------------------------------------------------------------
sub t1 + 19, #1 wc
Ch20 if_c xor Ch + 19, Mask + 19 wz
if_c_and_z mov t1 + 19, T_Off_Ch + 19
if_c_and_nz mov t1 + 19, T_On__Ch + 19
'-------------------------------------------------------------
sub t1 + 20, #1 wc
Ch21 if_c xor Ch + 20, Mask + 20 wz
if_c_and_z mov t1 + 20, T_Off_Ch + 20
if_c_and_nz mov t1 + 20, T_On__Ch + 20
'-------------------------------------------------------------
sub t1 + 21, #1 wc
Ch22 if_c xor Ch + 21, Mask + 21 wz
if_c_and_z mov t1 + 21, T_Off_Ch + 21
if_c_and_nz mov t1 + 21, T_On__Ch + 21
'-------------------------------------------------------------
sub t1 + 22, #1 wc
Ch23 if_c xor Ch + 22, Mask + 22 wz
if_c_and_z mov t1 + 22, T_Off_Ch + 22
if_c_and_nz mov t1 + 22, T_On__Ch + 22
'-------------------------------------------------------------
sub t1 + 23, #1 wc
Ch24 if_c xor Ch + 23, Mask + 23 wz
if_c_and_z mov t1 + 23, T_Off_Ch + 23
if_c_and_nz mov t1 + 23, T_On__Ch + 23
'-------------------------------------------------------------
sub t1 + 24, #1 wc
Ch25 if_c xor Ch + 24, Mask + 24 wz
if_c_and_z mov t1 + 24, T_Off_Ch + 24
if_c_and_nz mov t1 + 24, T_On__Ch + 24
'-------------------------------------------------------------
sub t1 + 25, #1 wc
Ch26 if_c xor Ch + 25, Mask + 25 wz
if_c_and_z mov t1 + 25, T_Off_Ch + 25
if_c_and_nz mov t1 + 25, T_On__Ch + 25
'-------------------------------------------------------------
sub t1 + 26, #1 wc
Ch27 if_c xor Ch + 26, Mask + 26 wz
if_c_and_z mov t1 + 26, T_Off_Ch + 26
if_c_and_nz mov t1 + 26, T_On__Ch + 26
'-------------------------------------------------------------
sub t1 + 27, #1 wc
Ch28 if_c xor Ch + 27, Mask + 27 wz
if_c_and_z mov t1 + 27, T_Off_Ch + 27
if_c_and_nz mov t1 + 27, T_On__Ch + 27
'-------------------------------------------------------------
sub t1 + 28, #1 wc
Ch29 if_c xor Ch + 28, Mask + 28 wz
if_c_and_z mov t1 + 28, T_Off_Ch + 28
if_c_and_nz mov t1 + 28, T_On__Ch + 28
'-------------------------------------------------------------
sub t1 + 29, #1 wc
Ch30 if_c xor Ch + 29, Mask + 29 wz
if_c_and_z mov t1 + 29, T_Off_Ch + 29
if_c_and_nz mov t1 + 29, T_On__Ch + 29
'-------------------------------------------------------------
sub t1 + 30, #1 wc
Ch31 if_c xor Ch + 30, Mask + 30 wz
if_c_and_z mov t1 + 30, T_Off_Ch + 30
if_c_and_nz mov t1 + 30, T_On__Ch + 30
'-------------------------------------------------------------
sub t1 + 31, #1 wc
Ch32 if_c xor Ch + 31, Mask + 31 wz
if_c_and_z mov t1 + 31, T_Off_Ch + 31
if_c_and_nz mov t1 + 31, T_On__Ch + 31
'-------------------------------------------------------------
'' Update IO Pins
mov temp, Ch + 00 ' Clear and initialize temp
or temp, Ch + 01 ' OR the remaining
or temp, Ch + 02 ' channels into temp
or temp, Ch + 03
or temp, Ch + 04
or temp, Ch + 05
or temp, Ch + 06
or temp, Ch + 07
or temp, Ch + 08
or temp, Ch + 09
or temp, Ch + 10
or temp, Ch + 11
or temp, Ch + 12
or temp, Ch + 13
or temp, Ch + 14
or temp, Ch + 15
or temp, Ch + 16
or temp, Ch + 17
or temp, Ch + 18
or temp, Ch + 19
or temp, Ch + 20
or temp, Ch + 21
or temp, Ch + 22
or temp, Ch + 23
or temp, Ch + 24
or temp, Ch + 25
or temp, Ch + 26
or temp, Ch + 27
or temp, Ch + 28
or temp, Ch + 29
or temp, Ch + 30
or temp, Ch + 31
mov outa, temp ' Move temp to the IOs
'-------------------------------------------------------------
''Check if Command is waiting
rdlong temp, cmd_address wz ' Read command
if_z jmp #Main_PWM_Loop ' Return to main PWM loop
'-------------------------------------------------------------
''Command Detected ; Check for valid Commands
xor temp, #UpdateTonToff wz,nr ' Check Ton/Toff
if_z jmp #_Update_TonToff
xor temp, #IO_State wz,nr ' Check IO state
if_z jmp #_Update_IO_State
xor temp, #DutyOverRide wz,nr ' Check DutyOverRide
if_z jmp #_DutyOverRide
xor temp, #SyncPhase wz,nr ' Check PhaseSync
if_z jmp #_SyncPhase
wrlong Zero, cmd_address ' Tell SPIN command has been processed
jmp #Main_PWM_Loop ' Return to main PWM loop
'-------------------------------------------------------------
_Update_TonToff
rdlong _Index, ARG_0 ' Read the selected pin number
rdlong _Ton, ARG_1 ' Read the TimeOn
add _Index, #T_Off_Ch ' Calculate database offset
movd Tf, _Index ' Self-modify TimeOn address
rdlong _Toff, ARG_2 ' Read the TimeOff
add _Index, #32 ' Calculate database offset
movd Tn, _Index ' Self-modify TimeOff address
wrlong Zero, cmd_address ' Tell SPIN command has been processed
Tn mov 00_00, _Ton ' Move TimeOn into modified address
Tf mov 00_00, _Toff ' Move TimeOff into modified address
jmp #Main_PWM_Loop ' Return to main PWM loop
'-------------------------------------------------------------
_Update_IO_State
rdlong _Index, ARG_0 ' Read the selected pin number
rdlong _PinValue, ARG_1 ' Read the desired pin State
mov temp, #1 ' Create mask for selected pin
shl temp, _Index
wrlong Zero, cmd_address ' Tell SPIN command has been processed
test _PinValue,#1 wz ' Check Pin State
nop
if_nz or dira, temp ' If State is '1' then set dira bit
if_z andn dira, temp ' If State is '0' then clear dira bit
jmp #Main_PWM_Loop ' Return to main PWM loop
'-------------------------------------------------------------
_DutyOverRide
rdlong _Index, ARG_0 ' Read the selected pin number
rdlong _OverRide, ARG_1 ' Read the desired OverRide State
mov temp, _Index ' Calculate Index
shl temp, #2 ' Multiply by 4 (<-this is the number of
' instructions between each Channel)
add temp, #Ch01 ' Add Calculated Index to Ch01 reference
movd ChModify, temp ' Self-modify Channel destination address
nop
mov temp, D_Default ' Force Default
xor _OverRide, #1 wz,nr ' Force 100% Duty
if_z mov temp, D_100
xor _OverRide, #2 wz,nr ' Force 0% Duty
if_z mov temp, D_0
ChModify movi 00_00 , temp ' Self-modify Duty Changes to Channel
wrlong Zero, cmd_address ' Tell SPIN command has been processed
jmp #Main_PWM_Loop ' Return to main PWM loop
'-------------------------------------------------------------
_SyncPhase
rdlong temp1, ARG_0 'Read Channel position 1
add temp1, #t1 'add database offset
movs Ch1_Read, temp1 'Self modify code to read proper position
nop 'delay for pipelining
Ch1_Read mov temp1, 00_00 'Read current value
rdlong temp2, ARG_2 'Read Phase Offset
add temp1, temp2 'Add Phase Offset
rdlong temp2, ARG_1 'Read Channel position 2
add temp2, #t1 'add database offset
movd Ch2_Write, temp2 'Self modify code to write proper position
nop 'delay for pipelining
Ch2_Write mov 00_00, temp1 'Write new value
wrlong Zero, cmd_address ' Tell SPIN command has been processed
jmp #Main_PWM_Loop ' Return to main PWM loop
''-------------------------Defined variables---------------------------------
D_100 long %011010_001 ' or - OpCode with Z flag cleared
D_0 long %011001_001 ' andn - Opcode with Z flag cleared
D_default long %011011_101 ' xor - Opcode with Z flag set
Zero long 0
temp2 long 0
cmd_address long 0
ARG_0 long 0
ARG_1 long 0
ARG_2 long 0
''---------------------Defined Aliased variables-----------------------------
temp ' Alias variable
temp1 ' Alias variable
long 0
_Index
_Ch1 ' Alias variable
long 0
_OverRide ' Alias variable
_PinValue ' Alias variable
_Ton ' Alias variable
_Ch2 ' Alias variable
long 0
_Toff ' Alias variable
_Phase ' Alias variable
long 0
''------------------Defined PWM specific variables---------------------------
Mask long |<0,|<1,|<2,|<3,|<4,|<5,|<6,|<7
long |<8,|<9,|<10,|<11,|<12,|<13
long |<14,|<15,|<16,|<17,|<18,|<19
long |<20,|<21,|<22,|<23,|<24,|<25
long |<26,|<27,|<28,|<29,|<30,|<31
t1 long 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
long 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
''----------------This Block of memory needs to stay intact-------------------
T_Off_Ch long 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
long 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
T_On__Ch long 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
long 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
''----------------------------------------------------------------------------
Ch long 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
long 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
{{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}}
{{
Controls MCP3204 and Sharp IR sensors. Total of 2 MCP3204 Chips and 6 Sharp IR sensors. Uses an on/off structure to minimize total cog usage.
Uses 1 cog for MCP3204 communication and 1 cog for F32. Stops each cog when not needed.
}}
CON
CLOCKS_PER_MICROSECOND = 5*16 ' simple xin*pll / 1_000_000
CLOCKS_PER_MILLISECOND = 5000*16 ' simple xin*pll / 1_000
samples = 1
OBJ
ADC : "MCP3204"
fMath : "F32"
VAR
''///////////////////////Variable for ADC Sharp IR Sensors////////////////////////
word IR1, IR2, IR3, IR4, IR5, IR6
PUB Stop 'Stop all objects
ADC.Stop
fMath.Stop
PUB Get_IR
ADC.start(17, 18, 19, 16, 4, 4, 12, 1) 'start 1st ADC IC and collect sensor samples
IR1 := ADC.average(0, samples)
IR2 := ADC.average(1, samples)
IR3 := ADC.average(2, samples)
IR4 := ADC.average(3, samples)
ADC.stop 'stop 1st ADC IC
ADC.start(22, 23, 24, 21, 4, 2, 12, 1) 'start 2nd ADC IC and collect sensor samples
IR5 := ADC.average(0, samples)
IR6 := ADC.average(1, samples)
ADC.stop 'stop 2nd ADC IC
'Delay_MS(100)
fMath.Start 'start floating point math object and linearize sensor values
IR1 := Linearizer(IR1)
IR2 := Linearizer(IR2)
IR3 := Linearizer(IR3)
IR4 := Linearizer(IR4)
IR5 := Linearizer(IR5)
IR6 := Linearizer(IR6)
fMath.Stop 'stop floation point math object
PUB Linearizer(IR_Sensor_Raw)
'equation used to linearize a logarithmic sensor output
'y = -74.3*ln(x) + 572.1
return ((fMath.FRound(fMath.FMul(fMath.FAdd(fMath.FMul(fMath.Log(fMath.FFloat(IR_Sensor_Raw)), fMath.FNeg(74.3)) , 577.1), 0.3937))#>9)<#85)
PUB IR_FR
return IR1
PUB IR_LE
return IR2
PUB IR_RI
return IR3
PUB IR_RR
return IR4
PUB IR_BO
return IR5
PUB IR_TO
return IR6
' /////////////////////////////////////////////////////////////////////////////
PUB Delay_MS( time )
{{
Delays "time" milliseconds and returns.
}}
waitcnt (cnt + time*CLOCKS_PER_MILLISECOND)
' // end Delay_MS
' /////////////////////////////////////////////////////////////////////////////
PUB Delay_US( time )
{{
Delays "time" microseconds and returns.
}}
waitcnt (cnt + time*CLOCKS_PER_MICROSECOND)
' // end Delay_US
'//////////////////////////////////////////////////////////////////////////////
{{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}}
CON
default_low_threshold = 100 ' default "low" threshold value (min: 0, max: 2^bits_s_in - 2)
default_high_threshold = 500 ' default "high" threshold value (min: 0, max: 2^bits_s_in - 2)
OBJ
VAR
BYTE cogon, cog
BYTE in_standby
LONG timescale
''===[ 49 longs, DO NOT alter order! ]===
LONG tx_pin
LONG rx_pin
LONG ck_pin
LONG cs_pin
LONG adcch
LONG channels
LONG bits_s_in
LONG mode
LONG par1
LONG par2
LONG ptr_start
LONG fastslow
LONG done
LONG channel
LONG duration
LONG retval
LONG count
LONG command
LONG chanstate[8]
LONG chanval[8]
LONG chanmax[8]
LONG chanmin[8]
PUB start (DTPin, INPin, ClkPin, RSPin, ChCount, ActChannels, BitCount, SingDiff)
'' Starts driver.
'' DTPin: outgoing (from µController) serial data pin (0-31); ignored if 1-channel ADC
'' INPin: incoming (to µController) serial data pin (0-31)
'' ClkPin: serial clock pin (0-31)
'' RSPin: reset, CS pin (0-31)
'' ChCount: Number of channels on ADC (3208/3008 = 8; 3204/3004 = 4; 3202/3002 = 2; 3201/3001 = 1)
'' ActChannels: number of channels to scan (1-8)
'' BitCount: number of bits the ADC outputs (2-16)
'' SingDiff: ADC single or differential mode (1 single, 0 differential)
'=====[ SETTINGS ]==============
par1 := default_low_threshold ' default "low" threshold value (min: 0, max: 2^bits_s_in - 2)
par2 := default_high_threshold' default "high" threshold value (min: 0, max: 2^bits_s_in - 2)
'===============================
stop
longfill(@done, 0, 38)
longmove(@tx_pin, @DTPin, 8)
ptr_start := 0
in_standby := 0
timescale := clkfreq / 1000 ' milliseconds -- could be changed to any scale
IF (((BitCount > 10 AND clkfreq => 80_000_000) OR (BitCount > 12 AND clkfreq => 64_000_000)) OR ChCount == 1)
fastslow := 0 ' slow mode
ELSE
fastslow := -1 ' fast mode
cogon := (cog := cognew(@entry, @tx_pin))
waitcnt(7000 + cnt) ' wait for driver to fully initialize
RETURN cogon
PUB start_pointed (DTPin, INPin, ClkPin, RSPin, ChCount, ActChannels, BitCount, SingDiff, chanstate_ptr, chanval_ptr, chanmax_ptr, chanmin_ptr)
'' Starts the driver, but with 4 supplied 8-long blocks.
'' DTPin: outgoing (from µController) serial data pin (0-31); ignored if 1-channel ADC
'' INPin: incoming (to µController) serial data pin (0-31)
'' ClkPin: serial clock pin (0-31)
'' RSPin: reset, CS pin (0-31)
'' ChCount: Number of channels on ADC (3208/3008 = 8; 3204/3004 = 4; 3202/3002 = 2; 3201/3001 = 1)
'' ActChannels: number of channels to scan (1-8)
'' BitCount: number of bits the ADC outputs (2-16)
'' SingDiff: ADC single or differential mode (1 single, 0 differential)
''
'' Note: This type of start grants the ability to access channels states and values in a faster method (e.g.
'' adcstate[7] -- similar to ina[7]). It also allows for multiple objects to get the same information from
'' this object. Of course, only the object that started this driver has access to the normal functions
'' (threshold, freq, average, etc.).
'' If this method is used to start the driver, the getmax, getmin, getstate, and curval functions will not
'' operate as expected. Use the supplied variables for the values given by those functions.
'=====[ SETTINGS ]==============
par1 := default_low_threshold ' default "low" threshold value (min: 0, max: 2^bits_s_in - 2)
par2 := default_high_threshold' default "high" threshold value (min: 0, max: 2^bits_s_in - 2)
'===============================
stop
longfill(@done, 0, 38)
longmove(@tx_pin, @DTPin, 8)
ptr_start := -1
in_standby := 0
timescale := clkfreq / 1000 ' milliseconds -- could be changed to any scale
chanstate := chanstate_ptr
chanval := chanval_ptr
chanmax := chanmax_ptr
chanmin := chanmin_ptr
IF (((BitCount > 10 AND clkfreq => 80_000_000) OR (BitCount > 12 AND clkfreq => 64_000_000)) OR ChCount == 1)
fastslow := 0 ' slow mode
ELSE
fastslow := -1 ' fast mode
cogon := (cog := cognew(@entry, @tx_pin))
waitcnt(7000 + cnt) ' wait for driver to fully initialize
RETURN cogon
PUB stop
'' Stops cog if running
IF (cogon~)
cogstop(cog)
PUB setthreshold (low, high)
'' sets the high/low thresholds for all channels
par1 := low
par2 := high
command := 1
PUB resetmaxminall
'' reset maximum and minimum values on all channels (min set to 0 and max set to max ADC value based on bits_s_in)
command := 2
PUB resetmax (ch)
'' reset maximum value on this channel (set to 0)
channel := ch
command := 3
PUB resetmin (ch)
'' reset minimum value on this channel (set to max ADC value based on bits_s_in)
channel := ch
command := 4
PUB waithigh (ch, watchdog, waitmode)
'' wait until this channel is in high state (returns channel value at end of wait in case of watchdog timeout)
'' waitmode enables or disables acknolegment of current channel state. Meaning, if waitmode is 0 and the channel
'' is currently high, but the actual value is floating between the two thresholds it is not considered
'' high. In this mode, the method will only return when the channel's value has exceeded the high
'' threshold level. waitmode 1 will read current channel state to see if it is high, if it is it will
'' return immediately.
IF (_checkchannel(ch) == false)
RETURN false
done := 0
par1 := waitmode
duration := timescale * watchdog
channel := ch
command := 5
REPEAT UNTIL (done)
RETURN retval
PUB waitlow (ch, watchdog, waitmode)
'' wait until this channel is in low state (returns channel value at end of wait in case of watchdog timeout)
'' waitmode enables or disables acknolegment of current channel state. Meaning, if waitmode is 0 and the channel
'' is currently low, but the actual value is floating between the two thresholds it is not considered
'' low. In this mode, the method will only return when the channel's value is eqaul or below the threshold
'' level. waitmode 1 will read current channel state to see if it is low, if it is it will return
'' immediately.
IF (_checkchannel(ch) == false)
RETURN false
done := 0
par1 := waitmode
duration := timescale * watchdog
channel := ch
command := 6
REPEAT UNTIL (done)
RETURN retval
PUB getfreq (ch, watchdog, precision, highhold)
'' return frequency on this channel
'' Note: This function waits for a high-to-low transition, then starts a timer. Once 2 to-the-power-of precision
'' high-to-low transitions have been achieved, it averages the period lengths between the high-to-low
'' transitions and returns the period length in retval. The frequency is determined by dividing current
'' clock speed by the period length. 0 precision means it wait for only one frequency cycle, while 5 will
'' make it wait for 32 cycles then average the results.
'' highhold can reduce the function's resolution by requiring the channel's high-state to be held for a
'' certain number of cycles. 0 disables the feature.
IF (_checkchannel(ch) == false)
RETURN false
done := 0
par1 := precision
par2 := highhold
duration := timescale * watchdog
channel := ch
command := 7
REPEAT UNTIL (done)
RETURN clkfreq / retval
PUB average (ch, samples)
'' return average value of a channel for a certain number of samples
IF (_checkchannel(ch) == false)
RETURN false
done := 0
par1 := (samples #> 1)
channel := ch
command := 8
REPEAT UNTIL (done)
RETURN retval / samples
PUB average_time (ch, watchdog)
'' return average value of a channel for a certain period of time
IF (_checkchannel(ch) == false)
RETURN false
done := 0
duration := timescale * (watchdog #> 1) ' do not allow 0 as a value
channel := ch
command := 9
REPEAT UNTIL (done)
RETURN retval / count
PUB standby_enable (waitlength)
'' puts the ADC into standby mode, the ADC isn't sampled, and the cog is in waitcnt most of the time
'' Note: checks for standby disable command every waitlength cycles (higher values use slightly less power, but
'' the cog may have to wait before the next command can be issued
in_standby := -1
par1 := waitlength #> 27
command := 10
PUB standby_disable
'' pulls the ADC out of standby (just call send any command)
'' Note: The first command sent to the driver (after standby_enable) is ignored, but it will pull the driver out
'' of standby
IF (in_standby)
done := 0
command := -1
REPEAT UNTIL (done)
in_standby := 0
PUB getmax (ch)
'' return maximum value on this channel since last reset
RETURN chanmax[ch]
PUB getmin (ch)
'' return minimum value on this channel since last reset
RETURN chanmin[ch]
PUB getstate (ch)
'' returns current state of this channel (-1 == high or 0 == low)
RETURN chanstate[ch]
PUB getval (ch)
'' returns current value of this channel
RETURN chanval[ch]
PUB getsamples
'' returns number of samples taken during last operation
RETURN count
PRI _checkchannel (ch)
'' Check if cannel being accessed is being monitored (without this check, driver locks up)
IF (ch => channels)
RETURN false
RETURN true
DAT
ORG
'=====[ START ]=========================================
entry
'-----[ SETUP VALUES, PINS, AND TIMER ]-----------------
MOV p1, PAR
RDLONG p2, p1 ' get data-out (TX) pin
MOV DPin2, p2
MOV DPin, #1
SHL DPin, p2
ADD p1, #4
RDLONG p2, p1 ' get data-in (RX) pin
MOV NPin, #1
SHL NPin, p2
ADD p1, #4
RDLONG p2, p1 ' get clock (CLK) pin
MOV CPin2, p2
MOV CPin, #1
SHL CPin, p2
ADD p1, #4
RDLONG p2, p1 ' get reset (CS) pin
MOV CSPin, #1
SHL CSPin, p2
ADD p1, #4
RDLONG adcchs, p1 ' get ADC being used (8/4/2/1-channel ADC)
CMP adcchs, #1 WZ ' if 1 channel ADC
IF_NZ JMP #:skip1
MOV sval, #0
MOV dval, #0
MOV valplus1, #0
MOV nullbits, #3
JMP #:vdone
:skip1 CMP adcchs, #2 WZ ' if 2 channel ADC
IF_NZ JMP #:skip2
MOV sval, sval_2
MOV dval, dval_2
MOV valplus1, valplus1_2
MOV nullbits, #0
JMP #:vdone
:skip2 MOV sval, sval_8 ' if nothing else...assumed 8/4 channel ADC
MOV dval, dval_8
MOV valplus1, valplus1_8
MOV nullbits, #2
:vdone
ADD p1, #4
RDLONG chs, p1 ' get number of channels to monitor
MAX chs, adcchs ' limit channels scanned to channels on ADC
ADD p1, #4
RDLONG bitssin, p1 ' get number bits to shift in
MOV bitssin1, bitssin
SUB bitssin1, #1 ' number of bits to ignore (usually shift-in-bits minus 1)
ADD p1, #4
RDLONG p2, p1 WZ ' get mode: single/differential
IF_NZ MOV mval, sval ' single
IF_Z MOV mval, dval ' differential
ADD p1, #4
MOV pr1_addr, p1 ' get parameter1 address
RDLONG chlow, p1 ' set low threshold
ADD p1, #4
MOV pr2_addr, p1 ' get parameter2 address
RDLONG chhigh, p1 ' set high threshold
ADD p1, #4
RDLONG p3, p1 ' get start type (-1 == pointer, 0 == normal)
ADD p1, #4
RDLONG fs, p1 ' get speed mode (-1 == fast, 0 == slow)
ADD p1, #4
MOV done_addr, p1 ' get "completed" mark address
ADD p1, #4
MOV chl_addr, p1 ' output value address
ADD p1, #4
MOV wd_addr, p1 ' watchdog timeout address
ADD p1, #4
MOV out_addr, p1 ' output value address
ADD p1, #4
MOV count_addr, p1 ' get sample count value address
ADD p1, #4
MOV cmd_addr, p1 ' get input command address
TJNZ p3, #:pointer_start ' pointers
ADD p1, #4
MOV state_addr, p1 ' get state address
ADD p1, #32
MOV chval_addr, p1 ' get state address
ADD p1, #32
MOV max_addr, p1 ' get channel max address
ADD p1, #32
MOV min_addr, p1 ' get channel min address
JMP #:cont
:pointer_start ADD p1, #4
RDLONG state_addr, p1 ' get state address
ADD p1, #32
RDLONG chval_addr, p1 ' get state address
ADD p1, #32
RDLONG max_addr, p1 ' get channel max address
ADD p1, #32
RDLONG min_addr, p1 ' get channel min address
:cont
MOV OUTA, #0 ' set all low
MOV DIRA, #0 ' set all input
MOV OUTA, CSPin ' set CS pin high (inactive)
OR DIRA, CPin ' set pins we use to output
OR DIRA, CSPin ' set pins we use to output
MOV val, mval
MOV val2, val ' backup
MOV chmin, #1
TEST chmin, #1 WC ' set C
RCL chmin, bitssin1 ' rotate 1's into val_min to set 1 to all bits bitssin deep
MOV adcmax, chmin ' store maximum value
MOVD :setmax, #chmax ' probably not necessary, but just in case (only happens during setup)
MOV idx, #8 ' do to all eight channels
:setmax MOV chmax, #0 ' set to zero
ADD :setmax, dplus1 ' move pointer
DJNZ idx, #:setmax
MOVD :setmin, #chmin ' probably not necessary, but just in case (only happens during setup)
MOV idx, #8 ' do to all eight channels
:setmin MOV chmin, adcmax ' set to max value
ADD :setmin, dplus1 ' move pointer
DJNZ idx, #:setmin
MINS chlow, #0 ' make sure low value is not below 0
SUB adcmax, #1 ' reduce max by one so "high" is possible
MAXS chhigh, adcmax ' make sure high value is not above max ADC value
ADD adcmax, #1 ' return adcmax back to original location
MOV idx, #0 ' clear any possible values
MOV cmd, #0 ' clear any possible values
MOV output, #0 ' clear any possible values
MOV clkready, #0 ' clear any possible values
MOV strt, #0 ' clear any possible values
MOV roll, #0 ' clear any possible values
MOV curchl, #0 ' clear any possible values
CMP adcchs, #1 WZ
IF_NZ MOV CTRA, nco
IF_NZ ADD CTRA, DPin2 ' NCO on this pin number
TJZ fs, #mainloop ' if in slow mode, skip CTRB setup
MOV CTRB, nco
ADD CTRB, CPin2 ' NCO on this pin number
MOV FRQB, #1
'-----[ MAIN LOOP ]-------------------------------------
mainloop
MOV bits_in, bitssin ' set to reset value
MOV PHSA, val2 ' get backup
TJNZ fs, #fast_shift ' if fast mode, go to fast shift
MOV Bits, #4 ' 5 bit output
ANDN OUTA, CSPin ' set CS pin low (active)
'-----[ SHIFT COMMAND OUT ]-----------------------------
CMP adcchs, #1 WZ ' if single channel ADC, no info to give
IF_Z JMP #:skip
OR DIRA, DPin ' set pins we use to output
OR OUTA, CPin ' start clock cycle
ANDN OUTA, CPin ' end clock cycle
:shift_out_slow
SHL PHSA, #1 ' shift output value
OR OUTA, CPin ' start clock cycle
ANDN OUTA, CPin ' end clock cycle
DJNZ Bits, #:shift_out_slow
ANDN DIRA, DPin ' set pins we use to input (so same IO can be used for RX and TX)
:skip
'-----[ NULL BITS ]-------------------------------------
TJZ nullbits, #:cont ' if zero ignore bits, skip this
MOV emptyclk, nullbits ' ignore bits
:empty ' generate empty clocks to ditch unwanted bits
OR OUTA, CPin ' start clock cycle
ANDN OUTA, CPin ' end clock cycle
DJNZ emptyclk, #:empty
:cont
'-----[ SHIFT MSB VALUE IN ]----------------------------
MOV val_out, #0 ' set to reset value (0)
shift_in_slow
TEST NPin, INA WC ' if data input pin is high
RCL val_out, #1 ' add input pin value to output
OR OUTA, CPin ' start clock cycle
ANDN OUTA, CPin ' end clock cycle
''NOP ' slow down input (slowest part of ADC) add this NOP if not reading information at 80MHz (or faster)
DJNZ bits_in, #shift_in_slow ' continue to end of input value
'-----[ SHIFT LSB IN AND IGNORE ]-----------------------
CMP adcchs, #2 WZ ' if 2-channel ADC
IF_NZ CMP adcchs, #1 WZ ' or 1-channel ADC
IF_Z JMP #:skip ' skip the ignore bits
MOV emptyclk, bitssin1
:empty ' generate empty clocks to ditch unwanted bits
OR OUTA, CPin ' start clock cycle
ANDN OUTA, CPin ' end clock cycle
DJNZ emptyclk, #:empty
:skip OR OUTA, CSPin ' set CS pin high (inactive)
JMP #maxch1
'=====[ END OF SLOW SHIFT ]=============================
fast_shift
ANDN OUTA, CSPin ' set CS pin low (active)
'-----[ SHIFT COMMAND OUT ]-----------------------------
OR DIRA, DPin ' set pins we use to output
shift_out
NEG PHSB, #3 ' Send a pulse 3 clocks long
SHL PHSA, #1 ' shift output value
NEG PHSB, #3 ' Send a pulse 3 clocks long
SHL PHSA, #1 ' shift output value
NEG PHSB, #3 ' Send a pulse 3 clocks long
SHL PHSA, #1 ' shift output value
NEG PHSB, #3 ' Send a pulse 3 clocks long
SHL PHSA, #1 ' shift output value
NEG PHSB, #3 ' Send a pulse 3 clocks long
ANDN DIRA, DPin ' set pins we use to input (so same IO can be used for RX and TX)
'-----[ NULL BITS ]-------------------------------------
TJZ nullbits, #:cont ' if zero ignore bits, skip this
MOV emptyclk, nullbits ' ignore bits
:empty ' generate empty clocks to ditch unwanted bits
NEG PHSB, #3 ' Send a pulse 3 clocks long
DJNZ emptyclk, #:empty
:cont
'-----[ SHIFT MSB VALUE IN ]----------------------------
MOV val_out, #0 ' set to reset value (0)
shift_in
TEST NPin, INA WC ' if data input pin is high
RCL val_out, #1 ' add input pin value to output
NEG PHSB, #3 ' Send a pulse 3 clocks long
DJNZ bits_in, #shift_in ' continue to end of input value
'-----[ SHIFT LSB IN AND IGNORE ]-----------------------
CMP adcchs, #2 WZ ' if 2-channel ADC
IF_Z JMP #:skip ' skip the ignore bits
MOV emptyclk, bitssin1
:empty ' generate empty clocks to ditch unwanted bits
NEG PHSB, #3 ' Send a pulse 3 clocks long
DJNZ emptyclk, #:empty
:skip OR OUTA, CSPin ' set CS pin high (inactive)
'=====[ END OF FAST SHIFT ]=============================
'-----[ DETERMINE MAX/MIN VALUE ]-----------------------
maxch1 MIN chmax, val_out ' set val_max to whichever is highest
minch1 MAX chmin, val_out ' set val_min to whichever is lowest
'-----[ READ COMMANDS ]---------------------------------
TJNZ cmd, #check_cmd ' check if command already is set
RDLONG cmd, cmd_addr WZ ' read input command
IF_Z JMP #no_mode
RDLONG p1, pr1_addr ' get applicable parameter1
RDLONG p2, pr2_addr ' get applicable parameter2
RDLONG sectime, wd_addr ' get applicable watchdog timer limit
RDLONG chl, chl_addr ' get applicable channel
WRLONG zero, cmd_addr ' clear input command value
'-----[ EXCECUTE COMMANDS ]-----------------------------
check_cmd
CMP cmd, #1 WZ
IF_Z JMP #set_thresh
CMP cmd, #2 WZ
IF_Z JMP #reset_maxmin
CMP cmd, #3 WZ
IF_Z JMP #reset_max
CMP cmd, #4 WZ
IF_Z JMP #reset_min
CMP cmd, #10 WZ
IF_Z JMP #standby
CMP chl, curchl WZ ' if current channel does not match command channel skip the rest
IF_NZ JMP #no_mode
CMP cmd, #5 WZ
IF_Z JMP #wait_high
CMP cmd, #6 WZ
IF_Z JMP #wait_low
CMP cmd, #7 WZ
IF_Z JMP #det_freq
CMP cmd, #8 WZ
IF_Z JMP #avg_samp
CMP cmd, #9 WZ
IF_Z JMP #avg_time
no_mode
end_mode
'-----[ END OF LOOP ]-----------------------------------
WRLONG val_out, chval_addr ' put current value in channel's value
ADD curchl, #1 ' move current channel one position
CMP val_out, chlow WZ, WC
IF_BE WRLONG zero, state_addr
ADD val2, valplus1 ' add one channel to value
CMP val_out, chhigh WZ, WC
IF_A WRLONG negone, state_addr
ADD state_addr, #4 ' move one long
ADD chval_addr, #4 ' move one long
maxch2 WRLONG chmax, max_addr ' max voltage over the second-long sample
ADD max_addr, #4 ' move one long
ADD maxch1, dplus1 ' move one destination
minch2 WRLONG chmin, min_addr ' min voltage over the second-long sample
ADD min_addr, #4 ' move one long
ADD minch1, dplus1 ' move one destination
ADD maxch2, dplus1 ' move one destination
ADD minch2, dplus1 ' move one destination
ADD roll, #4
CMP curchl, chs WZ, WC ' if it hasn't exceded the number of channels to be scanned
IF_B JMP #mainloop
MOV curchl, #0 ' set current channel to 0
MOV val2, mval ' add one channel to value
SUB state_addr, roll ' move back eight longs
SUB chval_addr, roll ' move back eight longs
SUB max_addr, roll ' move back eight longs
SUB min_addr, roll ' move back eight longs
MOVD maxch1, #chmax ' move back to original destination
MOVD maxch2, #chmax ' move back to original destination
MOVD minch1, #chmin ' move back to original destination
MOVD minch2, #chmin ' move back to original destination
MOV roll, #0
JMP #mainloop ' do it again!
'=====[ SUBROUTNIES ]===================================
'-----[ SET ALL CHANNELS' THRESHOLD ]-------------------
set_thresh
MINS p1, #0 ' make sure low value is not below 0
SUB adcmax, #1 ' reduce max by one so "high" is possible
MAXS p2, adcmax ' make sure high value is not above max ADC value
ADD adcmax, #1 ' return adcmax back to original location
CMP p1, p2 WZ, WC ' make sure low is not above high
IF_A MOV p3, p1
IF_A MOV p1, p2
IF_A MOV p2, p3
MOV chlow, p1 ' set low
MOV chhigh, p2 ' set high
MOV cmd, #0
JMP #end_mode
'-----[ RESET ALL MAX/MINS ]----------------------------
reset_maxmin
MOVD :setmax, #chmax ' clear any previous movement
MOV idx, #8 ' repeat for all 8 locations
:setmax MOV chmax, #0 ' clear max in current address
ADD :setmax, dplus1 ' move up one address
DJNZ idx, #:setmax
MOVD :setmin, #chmin ' clear any previous movement
MOV idx, #8 ' repeat for all 8 locations
:setmin MOV chmin, adcmax ' clear min in current address
ADD :setmin, dplus1 ' move up one address
DJNZ idx, #:setmin
MOV cmd, #0
JMP #end_mode
'-----[ RESET CHANNEL'S MAX ]---------------------------
reset_max
MOVD :setmax, #chmax ' clear any previous movement
MOV p1, chl ' get selected channel
SHL p1, #9
ADD :setmax, p1 ' move address to selected channel
MOV cmd, #0 ' need one instruction between setting and using modified instruction
:setmax MOV chmax, #0 ' clear max in current address
JMP #end_mode
'-----[ RESET CHANNEL'S MIN ]---------------------------
reset_min
MOVD :setmin, #chmin ' clear any previous movement
MOV p1, chl ' get selected channel
SHL p1, #9
ADD :setmin, p1 ' move address to selected channel
MOV cmd, #0 ' need one instruction between setting and using modified instruction
:setmin MOV chmin, adcmax ' clear min in current address
JMP #end_mode
'-----[ WAIT FOR CHANNEL HIGH ]-------------------------
wait_high
TJNZ strt, #:arstarted ' already started
CMP p1, #0 WZ ' if mode is not 0
IF_NZ RDLONG p3, state_addr WZ ' if current state is -1 (high)
IF_NZ JMP #:done ' say it is done
MOV output, #0 ' default low output
MOV idx, #0 ' set index count to 0
MOV strt, cnt ' start timer
NEG strt, strt ' get negative (used in place of SUB later on)
:arstarted
CMP val_out, chhigh WZ, WC
IF_BE JMP #check_watchdog ' if still low, check watchdog timer
:done MOV output, negone ' make output current value
JMP #alldone ' continue normal operation
'-----[ WAIT FOR CHANNEL LOW ]--------------------------
wait_low
TJNZ strt, #:arstarted ' already started
CMP p1, #0 WZ ' if mode is not 0
IF_NZ RDLONG p3, state_addr WZ ' if current state is 0 (low)
IF_Z JMP #:done ' say it is done
MOV output, negone ' default high output
MOV idx, #0 ' set index count to 0
MOV strt, cnt ' start timer
NEG strt, strt ' get negative (used in place of SUB later on)
:arstarted
CMP val_out, chlow WZ, WC
IF_A JMP #check_watchdog ' if still low, check watchdog timer
:done MOV output, #0 ' make output current value
JMP #alldone ' continue normal operation
'-----[ DETERMINE FREQUENCY ]---------------------------
det_freq
TJNZ strt, #:arstarted ' already started
MOV cumul, #0
MOV idx, #0
MOV clkready, #0
MOV track, #0
MOV tmstrt, #0
MOV freqcycls, p1 ' get exponent of times to double check frequency
MOV cyclesshl, #1
SHL cyclesshl, freqcycls ' move exponent value to left
MOV highhld, p2
MOV strt, cnt ' start timer
NEG strt, strt ' get negative (used in place of SUB later on)
:arstarted
CMP val_out, chhigh WZ, WC ' if current value is below or equal to "threshold value"
IF_BE JMP #vbelow
vabove
CMP track, highhld WZ, WC ' if val_out has been above "threshold value" for this many times
IF_AE MOV clkready, #1 ' set value to 1 so when val_out goes below "threshold value" we can clock a Hz
IF_AE JMP #check_watchdog
ADD track, #1
JMP #check_watchdog
vbelow
MOV track, #0 ' clear tracking value
CMP val_out, chlow WZ, WC
IF_A JMP #check_watchdog
CMP clkready, #0 WZ, WC
MOV clkready, #0 ' reset clock ready value
IF_BE JMP #check_watchdog
ADD cumul, #1 ' add a clock to Hz value
CMP cumul, cyclesshl WZ, WC
IF_BE JMP #:again
MOV p1, cnt ' stop timer
SUB p1, tmstrt ' get difference between timer start and end
'SHR p1, freqcycls ' divide to get average period (clock speed divided by period == frequency)
TJZ freqcycls, #:skip
SUB freqcycls, #1
SHR p1, freqcycls ' divide to get average period (clock speed divided by period == frequency)
SHR p1, #1 WC ' put "half" bit in c
ADDX p1, #0 ' if "half" bit is set, round up
:skip
MOV output, p1
JMP #alldone
:again
TJNZ tmstrt, #check_watchdog
MOV tmstrt, cnt ' start first clock cycle timer
JMP #check_watchdog
'-----[ COLLECT SUM OF VALUES TO AVERAGE ]--------------
avg_samp
TJNZ strt, #:arstarted ' already started
MOV cumul, #0
MOV idx, #0
:arstarted
ADD cumul, val_out ' add current value to existing output
ADD strt, #1
CMP strt, p1 WZ, WC ' if enough samples have been taken
ADD idx, #1
IF_AE MOV output, cumul
IF_AE JMP #alldone ' then we are all done
JMP #mainloop
'-----[ COLLECT SUM OF VALUES TO AVERAGE ]--------------
avg_time
TJNZ strt, #:arstarted ' already started
MOV cumul, #0
MOV idx, #0
MOV strt, cnt ' start timer
NEG strt, strt ' get negative (used in place of SUB later on)
:arstarted
ADD cumul, val_out
ADD idx, #1
MOV waitlen, cnt ' get now
ADDS waitlen, strt ' difference of start and now
CMP waitlen, sectime WZ, WC ' if below one second...do another loop
IF_B JMP #mainloop ' do it again!
MOV output, cumul
JMP #alldone
'-----[ GO INTO STANDBY, WAIT FOR EXIT COMMAND ]--------
standby
MOV FRQB, #0 ' zero FRQB to prevent pin toggling
MOV waitlen, cnt
ADD waitlen, p1
:wait RDLONG cmd, cmd_addr WZ ' if no command yet
IF_Z WAITCNT waitlen, p1 ' wait for a time (longer the less power)
IF_Z JMP #:wait ' look for command again
CMP fs, #0 WZ
IF_NZ MOV FRQB, #1 ' if fast mode, re-retup FRQB
WRLONG zero, cmd_addr ' clear input command value
MOV cmd, #0 ' space out wrlong (little bit faster)
WRLONG negone, done_addr
JMP #end_mode
'-----[ WATCHDOG ]--------------------------------------
check_watchdog
ADD idx, #1
TJZ sectime, #mainloop ' if watchdog is disabled...skip it
MOV waitlen, cnt ' get now
ADDS waitlen, strt ' difference of start and now
CMP waitlen, sectime WZ, WC ' if below one second...do another loop
IF_B JMP #mainloop ' do it again!
alldone
WRLONG output, out_addr ' output value
MOV strt, #0 ' clear any timer
WRLONG idx, count_addr ' number of loops to address
MOV cmd, #0
WRLONG negone, done_addr ' tell method, PASM command is done
MOV output, #0 ' if timed out, no output
JMP #end_mode ' move on to next channel
negone LONG -1 ' $FF_FF_FF_FF
zero LONG 0 ' used for cog memory writes
dplus1 LONG 1 << 9 ' destination plus one value
nco LONG %00100 << 26 ' numerically controlled oscillator counter setting
sval_8 LONG %11000 << 27 ' single-ended channel 0 output value (for 8/4 channel ADC)
dval_8 LONG %10000 << 27 ' differential channel 0 output value (for 8/4 channel ADC)
valplus1_8 LONG 1 << 27 ' add one channel (for 8/4 channel ADC)
sval_2 LONG %1101 << 28 ' single-ended channel 0 output value (for 2 channel ADC)
dval_2 LONG %1001 << 28 ' differential channel 0 output value (for 2 channel ADC)
valplus1_2 LONG 1 << 29 ' add one channel (for 2 channel ADC)
sval RES ' single-ended channel 0 output value
dval RES ' differential channel 0 output value
valplus1 RES ' add one channel
nullbits RES ' null bits between output and input
DPin RES ' tx
DPin2 RES ' tx
CPin RES ' clock
CPin2 RES ' clock
CSPin RES ' cs (reset)
NPin RES ' rx
sectime RES ' watchdog period
waitlen RES ' tmp time
tmstrt RES ' start timer at first clock
emptyclk RES ' number of empty clocks to excecute
out_addr RES ' output address
max_addr RES ' output address
min_addr RES ' output address
done_addr RES ' output address
count_addr RES ' output address
state_addr RES ' output address
chval_addr RES ' output address
cmd_addr RES ' input address
chl_addr RES ' input address
pr1_addr RES ' input address
pr2_addr RES ' input address
wd_addr RES ' input address
cmd RES ' command value
chl RES ' channel value
curchl RES ' current cycle's channel
output RES ' output value
cumul RES ' output value cumulator
clkready RES ' ready to add one to frequency ("high" criteria met)
track RES ' store number of times above "threshold value"
strt RES ' watchdog start time
idx RES
adcmax RES ' maximum ADC value based on number of bits_s_in
chs RES ' number of channels to scan (1-8)
roll RES ' number of bytes to roll back (when looping back to channel 0)
fs RES ' setting for runnnig fast or slow mode
adcchs RES ' track which ADC is being used (for different dataschemes)
Bits RES ' number of bits to shift out
bits_in RES ' number of value bits to shift in (10 or 12)
mval RES ' stored value for single/differential changes
val RES ' output value (channel number plus mval)
val2 RES ' "backup" of val
val_out RES ' shifted in value
freqcycls RES ' number of cycles to count for frequency
cyclesshl RES ' number of shifts to act as a fast divider
highhld RES ' number of cycles to see as "high" before alowing a "low" value to count a Hz
bitssin RES ' number of value bits to shift in (10 or 12)
bitssin1 RES ' number of value bits to shift in minus 1 (9 or 11)
p1 RES ' address pointer (for value/pin/address setup) and parameter1
p2 RES ' temperary value read from address and parameter2
p3 RES ' tmp value storrage
chhigh RES ' all channels' threshold before considered "high"
chlow RES ' all channels' threshold before considered "low"
chmax RES 8 ' channel's maxinum value since last reset
chmin RES 8 ' channel's minimum value since last reset
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. │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
}}
{{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}}
{{
*************************************
* Clock v1.1 *
* Author: Jeff Martin *
* Copyright (c) 2006 Parallax, Inc. *
* See end of file for terms of use. *
*************************************
Provides clock timing functions to:
• Set clock mode/frequency at run-time using the same clock setting constants as with _CLKMODE,
• Pause execution in units of microseconds, milliseconds, or seconds,
• Synchronize code to the start of time-windows in units of microseconds, milliseconds, or seconds.
See "Theory of Operation" below for more information.
{{--------------------------REVISION HISTORY--------------------------
v1.1 - Updated 11/27/2006 to fix clock mode value when mode is XINPUT
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
}}
CON
WMin = 381 'WAITCNT-expression-overhead Minimum
VAR
long XINFreq 'Propeller XIN frequency
long SyncPoint 'Next sync point for WaitSync
PUB Init(XINFrequency)
{{Call this before first call to SetClock.
PARAMETERS: XINFrequency = Frequency (in Hz) that external crystal/clock is driving into XIN pin.
Use 0 if no external clock source connected to Propeller.
}}
XINFreq := XINFrequency
PUB SetClock(Mode): NewFreq | PLLx, XTALx, RCx
{{Set System Clock to Mode.
Exits without modifying System Clock if Mode is invalid.
PARAMETERS: Mode = a combination of RCSLOW, RCFAST, XINPUT, XTALx and PLLx clock setting constants.
RETURNS: New clock frequency.
}}
if Valid(Mode) 'If Mode is valid 'The following is the enumerated
RCx := Mode & $3 ' Get RCSLOW, RCFAST setting 'clock setting constants that are
XTALx := Mode >> 2 & $F ' Get XINPUT, XTAL1, XTAL2, XTAL3 setting 'used for the Mode parameter.
PLLx := Mode >> 6 & $1F ' Get PLL1X, PLL2X, PLL4X, PLL8X, PLL16X setting ' ┌──────────┬───────┬──────┐
' │ Clock │ │ Mode │
'┌───────────────────────────────── New CLK Register Value ─────────────────────────────────┐ ' │ Setting │ Value │ Bit │
'┌────── PLLENA & OSCENA (6&5) ─────┐ ┌── OSCMx (4:3) ───┐ ┌─────── CLKSELx (2:0) ───────┐ ' │ Constant │ │ │
Mode := $60 & (PLLx > 0) | $20 & (XTALx > 0) | >| (XTALx >> 1) << 3 | $12 >> (3 - RCx) & $3 + >| PLLx ' Calculate new clock mode (CLK Register Value) ' ├──────────┼───────┼──────┤
'└── any PLLx? ─┘ └ XTALx/XINPUT? ┘ └───── XTALx ──────┘ └── RCx and XINPUT ─┘ └─PLLx┘ ' │ PLL16x │ 1024 │ 10 │
' │ PLL8x │ 512 │ 9 │
NewFreq := XINFreq*(PLLx#>||(RCx==0)) + 12_000_000*RCx&$1 + 20_000*RCx>>1 ' Calculate new system clock frequency ' │ PLL4x │ 256 │ 8 │
' │ PLL2x │ 128 │ 7 │
if not ((clkmode < $20) and (Mode > $20)) ' If not switching from internal to external plus oscillator and PLL circuits ' │ PLL1x │ 64 │ 6 │
clkset(Mode, NewFreq) ' Switch to new clock mode immediately (and set new frequency) ' │ XTAL3 │ 32 │ 5 │
else ' Else ' │ XTAL2 │ 16 │ 4 │
clkset(Mode & $78 | clkmode & $07, clkfreq) ' Rev up the oscillator and PLL circuits first ' │ XTAL1 │ 8 │ 3 │
waitcnt(clkfreq / 100 + cnt) ' Wait 10 ms for them to stabilize ' │ XINPUT │ 4 │ 2 │
clkset(Mode, NewFreq) ' Then switch to external clock (and set new frequency) ' │ RCSLOW │ 2 │ 1 │
' │ RCFAST │ 1 │ 0 │
NewFreq := clkfreq 'Return clock frequency ' └──────────┴───────┴──────┘
PUB PauseUSec(Duration)
{{Pause execution in microseconds.
PARAMETERS: Duration = number of microseconds to delay.
}}
waitcnt(((clkfreq / 1_000_000 * Duration - 3928) #> WMin) + cnt)
PUB PauseMSec(Duration)
{{Pause execution in milliseconds.
PARAMETERS: Duration = number of milliseconds to delay.
}}
waitcnt(((clkfreq / 1_000 * Duration - 3932) #> WMin) + cnt)
PUB PauseSec(Duration)
{{Pause execution in seconds.
PARAMETERS: Duration = number of seconds to delay.
}}
waitcnt(((clkfreq * Duration - 3016) #> WMin) + cnt)
PUB MarkSync
{{Mark reference time for synchronized-delay time windows.
Use one of the WaitSync methods to sync to start of next time window.
}}
SyncPoint := cnt
PUB WaitSyncUSec(Width)
{{Sync to start of next microsecond-based time window.
Must call MarkSync before calling WaitSyncUSec the first time.
PARAMETERS: Width = size of time window in microseconds.
}}
waitcnt(SyncPoint += (clkfreq / 1_000_000 * Width) #> WMin)
PUB WaitSyncMSec(Width)
{{Sync to start of next millisecond-based time window.
Must call MarkSync before calling WaitSyncMSec the first time.
PARAMETERS: Width = size of time window in milliseconds.
}}
waitcnt(SyncPoint += (clkfreq / 1_000 * Width) #> WMin)
PUB WaitSyncSec(Width)
{{Sync to start of next second-based time window.
Must call MarkSync before calling WaitSyncSec the first time.
PARAMETERS: Width = size of time window in seconds.
}}
waitcnt(SyncPoint += (clkfreq * Width) #> WMin)
PRI Valid(Mode): YesNo
{Returns True if Mode (combined with XINFreq) is a valid clock mode, False otherwise.}
YesNo := OneBit(Mode & $03F) and OneBit(Mode & $7C3) and not ((Mode & $7C0) and not (Mode & $3C)) and not ((XINFreq == 0) and (Mode & $3C <> 0))
PRI OneBit(Bits): YesNo
{Returns True if Bits has less than 2 bits set, False otherwise.
This is a "mutually-exclusive" test; if any bit is set, all other bits must be clear or the test fails.}
YesNo := Bits == |< >| Bits >> 1
{{
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
THEORY OF OPERATION
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Use this object to control the system clock and cog execution.
┌─────────────────┬──────────────┐
│ Valid Clock │ CLK Register │
│ Modes │ Value │
├─────────────────┼──────────────┤
│ RCFAST │ 0_0_0_00_000 │
TO SET THE SYSTEM CLOCK AT RUN-TIME: ├─────────────────┼──────────────┤
│ RCSLOW │ 0_0_0_00_001 │
├─────────────────┼──────────────┤
STEP 1: Call Init with the frequency (in Hz) of the external │ XINPUT │ 0_0_1_00_010 │
crystal/clock on the XIN pin (if any). For example, ├─────────────────┼──────────────┤
use Init(5_000_000) to specify an XIN pin frequency │ XTAL1 │ 0_0_1_01_010 │
of 5 MHz. │ XTAL2 │ 0_0_1_10_010 │
│ XTAL3 │ 0_0_1_11_010 │
STEP 2: Call SetClock with the new clock mode to switch to; ├─────────────────┼──────────────┤
expressed in clock setting constants similar to how the │ XINPUT + PLL1X │ 0_1_1_00_011 │
_CLKMODE constant is defined for the application's initial │ XINPUT + PLL2X │ 0_1_1_00_100 │
clock setting. For example, use SetClock(XTAL1 + PLL4X) │ XINPUT + PLL4X │ 0_1_1_00_101 │
to switch the System Clock to an external low-speed crystal │ XINPUT + PLL8X │ 0_1_1_00_110 │
source and wind it up by 4 times. │ XINPUT + PLL16X │ 0_1_1_00_111 │
├─────────────────┼──────────────┤
The table on the right shows all valid clock setting constants as │ XTAL1 + PLL1X │ 0_1_1_01_011 │
well as the CLK Register bit patterns they correspond to. │ XTAL1 + PLL2X │ 0_1_1_01_100 │
│ XTAL1 + PLL4X │ 0_1_1_01_101 │
NOTE: The SetClock method automatically validates the clock mode │ XTAL1 + PLL8X │ 0_1_1_01_110 │
settings, calculates and updates the System Clock Frequency value │ XTAL1 + PLL16X │ 0_1_1_01_111 │
(CLKFREQ) and performs the appropriate stabilization procedure, as ├─────────────────┼──────────────┤
needed, to ensure a stable clock when switching between internal │ XTAL2 + PLL1X │ 0_1_1_10_011 │
and external clock sources. In addition to the required 10 ms │ XTAL2 + PLL2X │ 0_1_1_10_100 │
stabilization period for internal-to-external clock source switches, │ XTAL2 + PLL4X │ 0_1_1_10_101 │
an additional delay of approximately 75 µs occurs while the │ XTAL2 + PLL8X │ 0_1_1_10_110 │
hardware switches the source. │ XTAL2 + PLL16X │ 0_1_1_10_111 │
├─────────────────┼──────────────┤
│ XTAL3 + PLL1X │ 0_1_1_11_011 │
│ XTAL3 + PLL2X │ 0_1_1_11_100 │
│ XTAL3 + PLL4X │ 0_1_1_11_101 │
│ XTAL3 + PLL8X │ 0_1_1_11_110 │
│ XTAL3 + PLL16X │ 0_1_1_11_111 │
└─────────────────┴──────────────┘
TO PAUSE EXECUTION BRIEFLY:
STEP 1: Call PauseUSec, PauseMSec, or PauseSec to pause for durations in units of microseconds, milliseconds,
or seconds, respectively.
NOTE: The Pause methods automatically do the following:
• Adjusts for System Clock changes so that their duration is consistent as long as the System Clock
frequency does not change during a pause operation itself.
• Adjusts the specified duration down to compensate for the Spin Interpreter overhead of calling the
method, performing the delay, and returning from the method. This is so the effect of a Pause statement
is a delay that is as close to the desired delay as possible, rather than being the desired delay plus
the call/return delay. The actual delay will vary slightly depending on the expression used for the
duration.
• Limits the minimum duration to a "Spin Interpreter" safe value that will not cause apparent "lock ups"
associated with waiting for a System Counter value that has already passed.
Keep in mind that System Clock frequency can greatly affect the shortest durations that are possible. For example,
in Spin code, while running at 80 MHz, the shortest duration for PauseUSec is about 54 (54 microseconds), but it
can reliably delay for 55 µs, 56 µs, 57 µs, etc. beyond that lower limit. When running at 20 KHz, the shortest
that PauseMSec can delay is about 216 (216 milliseconds), but it can reliably delay for 217 ms, 218 ms, etc.
TO SYNCHRONIZE A COMMAND/ROUTINE TO A WINDOW OF TIME (Synchronized Delays):
STEP 1: Call MarkSync to mark the reference point in time.
STEP 2: Call WaitSyncUSec, WaitSyncMSec, or WaitSyncSec immediately before the command/routine you wish to
synchronize, to wait for the start of the next window of time (measured in units of microseconds,
milliseconds, or seconds, respectively).
NOTE: The WaitSync methods automatically do the following:
• Adjusts for System Clock changes so that their time-window width is consistent as long as the System
Clock frequency does not change during a wait operation itself.
• Limits the minimum width to a "Spin Interpreter" safe value that will not cause apparent "lock ups"
associated with waiting for a System Counter value that has already passed.
In loops, the MarkSync/WaitSync methods (Synchronized Delays) have an advantage over the Pause methods in
that they automatically compensate for the loop's overhead so that the command following the WaitSync
executes at the exact same interval each time, even if the loop itself has multiple decision paths that
each take different amounts of time to execute.
For example, the following code uses PauseUSec in a loop that toggles a pin (assume T is this Clock object
and we are using an accurate, external clock source):
dira[16]~~
repeat
T.PauseUSec(100)
!outa[16]
This produces a signal on P16 that looks similar to the following timing diagram.
P16 ─
...
0 100 200 300 400 500 600 700 800 900
Time (µS)
The pause of 100 µS reliably delays for 100 µS, but the rest of the loop (!outa[16] and repeat) take some
time to execute also, causing the rising and falling edges to be slightly off of our time reference window.
If the intention was for the rising and falling edges to be exactly lined up with our time reference window
of 100 µS, then the MarkSync/WaitSync methods should be used. The following code performs the same toggling
task as the previous example (assume T is this Clock object and we are using an accurate, external clock source):
dira[16]~~
T.MarkSync
repeat
T.WaitSyncUSec(100)
!outa[16]
This produces a signal on P16 that looks similar to the following timing diagram.
P16 ─
...
0 100 200 300 400 500 600 700 800 900
Time (µS)
The MarkSync method marks a reference point in time (0 µS) and each call to WaitSyncUSec(100) waits until
the next multiple of 100 µS from that reference point. As long as the loop isn't too long, this effectively
compensates for the loop's overhead automatically, causing the !outa[16] statement to execute at exact 100 µS
intervals.
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ 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. │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
}}
{{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}}
''*****************************
''* Sparecogs by Alex *
''*****************************
VAR
byte cog[8]
PUB freecount : count | i, c
''counts the free cogs returns the count and fills in a string to show which cogs are free and in use
i :=0
repeat 'loads as many cogs as possible and stores their cog numbers
c := cog[i] := cognew(@entry, 0)
if c=>0
i++
while c => 0
count:=i 'return the count
repeat 'unloads the cogs and updates the string
i--
if i=>0
cstr[cog[i]]:=cog[i]+48
cogstop(cog[i])
while i=>0
PUB freestring : fstr
''returns the string of the cogs free (numbered) and in use (dotted)
freecount 'calls freecount to update the string
fstr := @cstr
DAT
org
'
' Entry
'
entry jmp entry 'just loops
cstr byte "••••••••",0
{{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}}
CON
_clkmode = xtal1 + pll16x 'Use the PLL to multiple the external clock by 16
_xinfreq = 5_000_000 'An external clock of 5MHz. is used (80MHz. operation)
_MAC = $8000
_Gateway = $8006
_Subnet = $800A
_IP = $800E
_Port = $8012
_DHCP = $8014
EE = $A0
SDA = 29
SCL = 28
_WIZ_data0 = 0 'SPI Mode = MISO, Indirect Mode = data bit 0.
_bytebuffersize = 2048
_WIZ_addr0 = 8 'SPI Mode unused, Indirect Mode = address bit 0
_WIZ_addr1 = 9 'SPI Mode unused, Indirect Mode = address bit 1
_WIZ_wr = 10 'SPI Mode unused, Indirect Mode = /write
_WIZ_rd = 11 'SPI Mode unused, Indirect Mode = /read
_WIZ_cs = 12 'SPI Mode unused, Indirect Mode = /chip select
_WIZ_int = 13 'W5100 /interrupt
_WIZ_rst = 14 'W5100 chip reset
_WIZ_sen = 15 'W5100 low = indirect mode, high = SPI mode, floating will = high.
HTTP_TIMEOUT = 80_000_000 * 10 'x seconds at 80MHz
maxPacketSendSize = 2032
QUOTE = 34
CR = 13
LF = 10
VAR
word numvar
word direction
'Configuration variables for the W5100
byte MAC[6]
byte Gateway[4]
byte Subnet[4]
byte IP[4]
long localSocket
long socket
'Variables to info for where to return the data to
byte destIP[4]
long destSocket
'Misc variables
byte data[_bytebuffersize]
long PageCount
long fileErrorHandle
long dynamicContentPtr
byte debug
long timeoutCounter
byte isTimeoutTimerOn
long t1
long xmlCondition
''///////////////Variables From Flight Control////////////////////////
long GX_F, GY_F, GZ
long Heading, Bearing, cur_State
byte user_Input
long SData[9],RData[9]
long stack[100]
'***************************************
OBJ 'Object declaration to be located here
'***************************************
'Choose which driver to use by commenting/uncommenting the driver. Only one can be chosen.
W5100 : "W5100_Indirect_Driver"
PST : "Parallax Serial Terminal.spin"
Request : "HttpRequest.spin"
SDCard : "SD2.0_FATWrapper"
SDCard2 : "SD2.0_FATWrapper"
eeprom : "jm_i2c"
TxRx : "FullDuplexSerialPlus"
PUB main | i, fileSize, token, packetSize, startCnt, timeoutCnt, currentCnt, fileName[64], bodyLen, j, reset
'' First routine to be executed in the program
'' because it is first PUB in the file
TxRx.start(25, 24, 0, 57600)
cognew(Communicate, @stack[0])
'RR.start
numvar := 101
debug := 1
isTimeoutTimerOn := 0
eeprom.init(SCL, SDA)
'rtc.Start
PauseMSec(2_000) 'A small delay to allow time to switch to the terminal application after loading the device
SDCard.Start
PauseMSec(500)
PST.Start(115_200)
PageCount := 0
bytefill(@data, 0, 2048)
dynamicContentPtr := @dynamicContent
'Mount the SD card
SDCard.mount(fileErrorHandle)
'Start the W5100 driver
W5100.StartINDIRECT(_WIZ_data0, _WIZ_addr0, _WIZ_addr1, _WIZ_cs, _WIZ_rd, _WIZ_wr, _WIZ_rst, _WIZ_sen)
MAC[0] := $00
MAC[1] := $08
MAC[2] := $DC
MAC[3] := $16
MAC[4] := $EE
MAC[5] := $FE
'Subnet address to be assigned to W5100
Subnet[0] := 255
Subnet[1] := 255
Subnet[2] := 255
Subnet[3] := 0
'IP address to be assigned to W5100
IP[0] := 192
IP[1] := 168
IP[2] := 1
IP[3] := 120
'Gateway address of the system network
Gateway[0] := 192
Gateway[1] := 168
Gateway[2] := 1
Gateway[3] := 1
'Local socket
localSocket := 80
'Destination IP address - MUST BE SET TO THE SERVER TO CONNECT TO
destIP[0] := 0
destIP[1] := 0
destIP[2] := 0
destIP[3] := 0
destSocket := 80
'**************************************
' Begin
'**************************************
'Set the W5100 addresses
SetMac(@MAC[0])
SetGateway(@Gateway[0])
SetSubnet(@Subnet[0])
SetIP(@IP[0])
W5100.SocketOpen(0, W5100#_TCPPROTO, localSocket, destSocket, @destIP[0])
'Wait a moment for the socket to get established
PauseMSec(500)
if(debug)
PST.Str(string("--- Listening ---"))
PST.char(Request#CR)
W5100.SocketTCPlisten(0)
'Wait a moment for the socket to listen
PauseMSec(250)
PageCount := 1
'HTTP server service
repeat
SetupXML
' Initialize content pointer
dynamicContentPtr := @dynamicContent
't1 := 0
W5100.readIND(W5100#_S0_SR, @S0_SR, 1)
' PauseMSec(1)
if(debug)
SN_SR := S0_SR
PST.Str(string("****************************", Request#CR))
PST.Str(string("Status Code 1: "))
PST.Hex(S0_SR, 2)
PST.char(Request#CR)
PST.Str(string("****************************", Request#CR))
'WriteLog(string("Status Code 1: "))
'WriteLog(STR.numberToDecimal(S0_SR, 5))
'WriteLog(Request.NumberToAsccii(S0_SR))
'WriteLog(string(Request#CR))
if S0_SR == $00
ResetSocket
't1 := cnt + HTTP_TIMEOUT
reset := 0
repeat while !W5100.SocketTCPestablished(0)
'if(t1 > cnt)
W5100.readIND(W5100#_S0_SR, @S0_SR, 1)
ifnot((S0_SR == $14) OR (S0_SR == $16) OR (S0_SR == $17) OR (S0_SR == $11) )
PST.Str(string("Status Code 2: "))
PST.Hex(S0_SR, 2)
'WriteLog(string("Status Code 2: "))
'WriteLog(STR.numberToDecimal(S0_SR, 5))
'WriteLog(Request.NumberToAsccii(S0_SR))
'WriteLog(string(Request#CR))
reset := ResetSocket
quit
if(reset)
next
if(debug)
W5100.readIND(W5100#_S0_SR, @S0_SR, 1)
PST.Str(string("****************************", Request#CR))
PST.Str(string("Status Code 3: "))
PST.Hex(S0_SR, 2)
PST.char(Request#CR)
PST.Str(string("****************************", Request#CR))
'WriteLog(string("Status Code 3: "))
'WriteLog(STR.numberToDecimal(S0_SR, 5))
'WriteLog(Request.NumberToAsccii(S0_SR))
'WriteLog(string(Request#CR))
'Initialize the buffers
bytefill(@data, 0, _bytebuffersize)
' Get packet size and fill data
packetSize := W5100.rxTCP(0, @data)
' Debug
if(debug)
PST.Str(string("packetSize: "))
PST.dec(packetSize)
PST.char(Request#CR)
if (packetSize == 0)
PST.Str(string("****************************"))
PST.char(Request#CR)
if(isTimeoutTimerOn)
'Check if we timed out
if(timeoutCounter > cnt)
' We timed out
' reset members and socket
isTimeoutTimerOn := 0
timeoutCounter := 0
if(debug)
PST.Str(string("Timeout Alert:"))
PST.char(Request#CR)
'Log the time out
'WriteLog(string("Timeout Alert", Request#CR))
ResetSocket
else
' Set the timeout timer flag
' and initialize the timer mark.
isTimeoutTimerOn := 1
timeoutCounter := cnt + HTTP_TIMEOUT
if(debug)
PST.Str(string("Timer set: "))
PST.Dec(timeoutCounter)
PST.char(Request#CR)
if(debug)
PST.Str(string("****************************"))
PST.char(Request#CR)
else
' Initialize the request object
Request.Initialize(@data)
if(Request.GetMethod == Request#HTTP_POST)
bodyLen := Request.BodyLength
PST.Str(string("Body length: "))
pst.dec(bodyLen)
PST.char(Request#CR)
if(bodyLen < Request.GetContentLength)
repeat 5
packetSize := W5100.rxTCP(0, @data+strsize(@data))
PST.Str(string("Getting Body", Request#CR))
'WriteLog(string("Getting Body", Request#CR))
if( packetSize > 0)
quit
'Reset pointers
'Logger
'WriteLog(BuildTimeString)
'WriteLog(@data)
'pst.str(string("*************************",Request#CR))
'pst.dec(Request.GetUrlPtr)
'PST.char(Request#CR)
'pst.str(string("*************************",Request#CR))
if(debug)
PST.Str(string("File: "))
PST.Str(Request.Filename)
PST.char(Request#CR)
PST.Str(string("Count = "))
PST.Dec(PageCount++)
PST.char(Request#CR)
' Determine if we're executing dynamic or static content
DispatchProcess(Request.Filename)
' disconnect, close, and open the default socket
ResetSocket
return 'end of main
PRI WriteLog(dataPtr)
'SDCard.openFile(string("log.txt"), "a")
'SDCard.writeData(dataPtr, strsize(dataPtr))
' SDCard.closeFile
{
PRI BuildTimeString
bytefill(@clipboard, 0, 1024)
byte[clipboard++] := Request.NumberToAsccii(rtc.GetMonth)
byte[clipboard++] := "/"
byte[clipboard++] := Request.NumberToAsccii(rtc.GetDay)
byte[clipboard++] := "/"
byte[clipboard++] := Request.NumberToAsccii(rtc.GetYear)
byte[clipboard++] := " "
byte[clipboard++] := Request.NumberToAsccii(rtc.GetHour)
byte[clipboard++] := ":"
byte[clipboard++] := Request.NumberToAsccii(rtc.GetMinutes)
byte[clipboard++] := ":"
byte[clipboard++] := Request.NumberToAsccii(rtc.GetSeconds)
return @clipboard
}
PRI ResetSocket
'End the connection
PST.Str(string("Reset Socket"))
'WriteLog(string("Reset Socket"))
'WriteLog(string(Request#CR))
PST.char(Request#CR)
' Disconnect
W5100.SocketTCPdisconnect(0)
'Connection terminated
W5100.SocketClose(0)
'Once the connection is closed, need to open socket again
OpenSocketAgain
return -1
'dynamicContentPtr := @dynamicContent
'bytefill(@dynamicContent, 0, 1024)
'bytefill(@clipboard, 0, 1024)
't1 := cnt + HTTP_TIMEOUT
PRI DispatchProcess(filename) | type
{ }
' Check if we're requesting Spinneret configuration
'if(strcomp(filename, string("config.htm")))
'Config
''return
{
if(strcomp(filename, string("message.txt")))
Message
return
}
'if this is not a dynamic page then call StaticFileHandler
type := Request.GetFileType
ifnot(type == Request#DYNAMIC)
StaticFileHandler(filename)
' Call the appropriate dynamic method
if(type == Request#DYNAMIC)
'Map the file requested to a method
'and executes the method
if(MapMethod)
if(DynamicFileHandler(filename))
' Change directory to system/temp
' and serve up the temp.htm file
StaticFileHandler(string("temp.htm"))
else
StaticFileHandler(filename)
return
'DebugPage(STR.numberToDecimal(type, 5))
PRI ChangeDirectory | i
'Handle any directory structure in the request object
if(Request.GetDirectoryStackDepth > 0)
repeat i from 0 to Request.GetDirectoryStackDepth - 1
if(FileExists(Request.Directory(i)))
SDCard.changeDirectory(Request.Directory(i))
return true
else
return false
return true
PRI FileExists(fileToCompare) | filenamePtr
'Start file find at the top of the list
SDCard.startFindFile
'Verify that the file exists
filenamePtr := 1
repeat while filenamePtr <> 0
filenamePtr := SDCard.nextFindFile
if(Request.MatchPattern(fileToCompare, filenamePtr, false, 0 ) > -1 )
return true
return false
{
PRI ChangeToSystemTempDirectory
SDCard.changeDirectory(string("system"))
SDCard.changeDirectory(string("temp"))
}
PRI MapMethod
' Clear dynamic content
bytefill(@dynamicContent, 0, 1024)
PST.Str(string("MapMethod : "))
pst.str(Request.FilenameNoExtension)
PST.Str(string(Request#CR))
' Map a file name to a method
if(strcomp(Request.FilenameNoExtension, string("service")))
Service
return true
if(strcomp(Request.FilenameNoExtension, string("login")))
Login
return true
if(strcomp(Request.FilenameNoExtension, string("upload")))
Upload
return true
if(strcomp(Request.FilenameNoExtension, string("shp")))
Controls
return false
return false
PUB WriteTempFile
SDCard.changeDirectory(@approot)
SDCard.openFile(string("request.txt"), "a")
'Write data and close the file
SDCard.writeData(@data, strsize(@data))
SDCard.closeFile
return
PUB DynamicFileHandler(filename) | fileSize, i, offset, s
bytefill(@data, 0, 2048)
bytefill(@clipboard, 0, 1024)
'dynamicContent
ifnot(ChangeDirectory)
'send 404 error
HTTPError(404)
SDCard.changeDirectory(@approot)
return
ifnot(FileExists(filename))
'send 404 error
HTTPError(404)
SDCard.changeDirectory(@approot)
return
' Open the file for reading
SDCard.openFile(fileName, "r")
fileSize := SDCard.getFileSize
' Copy the file contents to memory
SDCard.readFromFile(@data, fileSize)
SDCard.closeFile
' Find the server tag to replace
offset := Request.MatchPattern(@data, @placeholder, true, 0)
ifnot(offset == -1)
result := true
' Point to the end of the server tag
' reference main memory
offset := offset + strsize(@placeholder)
s := offset + @data
' Cut the end of the file to the clipboard
bytemove(@clipboard, s, strsize(s))
offset := offset - strsize(@placeholder)
s := offset + @data
bytefill(s, 0, strsize(s))
'Append the dynamic content
'bytemove(s, @dynamicContent, strsize(@dynamicContent))
' Position the pointer after the dynamic content
' and append the clipboard
's := s + strsize(@dynamicContent)
'bytemove(s, @clipboard, strsize(@clipboard))
SDCard.deleteEntry(@tempFile)
SDCard.newFile(@tempFile)
SDCard.openFile(@tempFile, "w")
'fileSize := SDCard.getFileSize
' Create the dynamic page on the SD card
SDCard.writeData(@data, strsize(@data))
SDCard.writeData(@dynamicContent, strsize(@dynamicContent))
SDCard.writeData(@clipboard, strsize(@clipboard))
bytefill(@dynamicContent, 0, 1024)
bytefill(@clipboard, 0, 1024)
SDCard.closeFile
else
result := false
SDCard.changeDirectory(@approot)
return
PRI StaticFileHandler(fileName) | fileSize, i
ifnot(ChangeDirectory)
'send 404 error
HTTPError(404)
SDCard.changeDirectory(@approot)
return
ifnot(FileExists(filename))
'send 404 error
HTTPError(404)
SDCard.changeDirectory(@approot)
return
' Open the file for reading
SDCard.openFile(fileName, "r")
fileSize := SDCard.getFileSize
WriteResponseHeader
if fileSize < maxPacketSendSize
' send the file in one packet
SDCard.readFromFile(@data[0], fileSize)
W5100.txTCP(0, @data[0], fileSize)
'PauseMSec(1)
else
' send the file in a bunch of packets
repeat
SDCard.readFromFile(@data[0], maxPacketSendSize)
W5100.txTCP(0, @data[0], maxPacketSendSize)
fileSize -= maxPacketSendSize
' once the remaining fileSize is less then the max packet size, just send that remaining bit and quit the loop
if fileSize < maxPacketSendSize and fileSize > 0
SDCard.readFromFile(@data[0], fileSize)
W5100.txTCP(0, @data[0], fileSize)
quit
' Bailout
if(i++ > 1_000_000)
'DebugPage(string("StaticFileHandler bailout fired"))
quit
StringSend(0, string(Request#CR, Request#LF))
SDCard.closeFile
SDCard.changeDirectory(@approot)
return
{
PUB Message
StringSend(0, string("HTTP/1.1 200 OK"))
StringSend(0, string(Request#CR, Request#LF))
StringSend(0, string("Server: Spinneret/1.0"))
StringSend(0, string(Request#CR, Request#LF))
StringSend(0, string("Connection: close"))
StringSend(0, string(Request#CR, Request#LF))
StringSend(0, string("Cache-Control: public"))
StringSend(0, string(Request#CR, Request#LF))
StringSend(0, string("Content-disposition: attachment; filename=mesasge.txt"))
StringSend(0, string(Request#CR, Request#LF))
StringSend(0, string(Request#CR, Request#LF))
StringSend(0, string("Hello World!"))
return
}
PRI HTTPError(errorNumber)
if(errorNumber == 404)
StringSend(0, string("HTTP/1.1 404 Not Found"))
StringSend(0, string(Request#CR, Request#LF))
StringSend(0, string("Server: Spinneret/1.0"))
StringSend(0, string(Request#CR, Request#LF))
StringSend(0, string("Connection: close"))
StringSend(0, string(Request#CR, Request#LF))
StringSend(0, string("Cache-Control: private"))
StringSend(0, string(Request#CR, Request#LF))
StringSend(0, string("Vary: Accept-Encoding"))
StringSend(0, string(Request#CR, Request#LF))
StringSend(0, string(Request#CR, Request#LF))
StringSend(0, string("
404 Not Found
"))
StringSend(0, string("File not found.
"))
PRI WriteResponseHeader | fileSize
StringSend(0, string("HTTP/1.1 200 OK"))
StringSend(0, string(Request#CR, Request#LF))
StringSend(0, string("Server: Spinneret/1.0"))
StringSend(0, string(Request#CR, Request#LF))
StringSend(0, string("Connection: close"))
StringSend(0, string(Request#CR, Request#LF))
StringSend(0, string("Cache-Control: private"))
StringSend(0, string(Request#CR, Request#LF))
'StringSend(0, string("Vary: Accept-Encoding"))
'StringSend(0, string(Request#CR, Request#LF))
' pick a type base on the file extension
if strcomp(Request.FileExtension, string("htm"))
StringSend(0, string("Content-Type: text/html"))
elseif strcomp(Request.FileExtension, string("xml"))
StringSend(0, string("Content-Type: text/xml"))
elseif strcomp(Request.FileExtension, string("xsl"))
StringSend(0, string("Content-Type: text/xml"))
elseif strcomp(Request.FileExtension, string("shp"))
StringSend(0, string("Content-Type: text/html"))
elseif strcomp(Request.FileExtension, string("txt"))
StringSend(0, string("Content-Type: text/plain; charset=utf-8"))
'StringSend(0, string("Content-disposition: attachment"))
elseif strcomp(Request.FileExtension, string("pdf"))
StringSend(0, string("Content-Type: application/pdf"))
'StringSend(0, string("Content-Transfer-Encoding: binary"))
elseif strcomp(Request.FileExtension, string("zip"))
StringSend(0, string("Content-Type: application/zip"))
elseif strcomp(Request.FileExtension, string("jpg"))
StringSend(0, string("Content-Type: image/jpeg"))
elseif strcomp(Request.FileExtension, string("png"))
StringSend(0, string("Content-Type: image/png"))
elseif strcomp(Request.FileExtension, string("gif"))
StringSend(0, string("Expires: Wed, 01 Feb 2012 01:00:00 GMT", Request#CR, Request#LF))
StringSend(0, string("Content-Type: image/gif"))
elseif strcomp(Request.FileExtension, string("ico"))
StringSend(0, string("Expires: Wed, 01 Feb 2012 01:00:00 GMT", Request#CR, Request#LF))
StringSend(0, string("Content-Type: image/vnd.microsoft.icon"))
elseif strcomp(Request.FileExtension, string("css"))
StringSend(0, string("Expires: Wed, 01 Feb 2012 01:00:00 GMT", Request#CR, Request#LF))
StringSend(0, string("Content-Type: text/css"))
elseif strcomp(Request.FileExtension, string("js"))
StringSend(0, string("Expires: Wed, 01 Feb 2012 01:00:00 GMT", Request#CR, Request#LF))
StringSend(0, string("Content-Type: text/javascript"))
elseif strcomp(Request.FileExtension, string("wmv"))
StringSend(0, string("Content-Type: video/x-ms-wmv"))
StringSend(0, string("Content-disposition: attachment; filename=video/gwhale.wmv"))
StringSend(0, string("Content-length: 637605"))
else
StringSend(0, string("Content-Type: text/html"))
' Empty line
StringSend(0, string(Request#CR, Request#LF))
StringSend(0, string(Request#CR, Request#LF))
return
PRI Controls | temp, msg
temp := Request.__Get(string("directionstate"))
pst.str(string("//////////////////////////////////",Request#CR, Request#LF))
pst.str(string("Direction: "))
pst.str(temp)
pst.str(string(Request#CR, Request#LF))
pst.str(string("//////////////////////////////////", Request#CR, Request#LF))
if(strcomp(temp, string("0"))) ''''Match the variable to the specific commanded input, reference PUB Comm
LedStatus(1)
'user_Input := 1
elseif(strcomp(temp, string("1")))
LedStatus(0)
'user_Input := 2
elseif(strcomp(temp, string("2")))
LedStatus(1)
'user_Input := 3
elseif(strcomp(temp, string("3")))
LedStatus(0)
'user_Input := 4
'user_Input := 0
'WriteResponseHeader
if(strcomp(temp, string("0")))
'direction := 111
StringSend(0, string("Up", Request#CR, Request#LF))
elseif(strcomp(temp, string("1")))
StringSend(0, string("Left", Request#CR, Request#LF))
'direction := 222
elseif(strcomp(temp, string("2")))
StringSend(0, string("Right", Request#CR, Request#LF))
'direction := 222
elseif(strcomp(temp, string("3")))
StringSend(0, string("Down", Request#CR, Request#LF))
'direction := 222
else
StringSend(0, string("Waiting for input.", Request#CR, Request#LF))
'direction := 222
return
PRI LedStatus(state)
dira[23]~~
outa[23] := state
return
PRI Service | temp
temp := Request.__GET(string("log"))
if(strcomp(temp, string("delete")))
SDCard.changeDirectory(@approot)
SDCard.deleteEntry(string("log.txt"))
SDCard.newFile(string("log.txt"))
PushDynamicContent(string("Hello World!
"))
PushDynamicContent(string("This page was generated on the server.
"))
PushDynamicContent(string("Counter: "))
Request.NumberToAsccii(PageCount++)
PushDynamicContent(Request.GetAsciiNum)
'PushDynamicContent(STR.numberToDecimal(PageCount++, 5))
PushDynamicContent(string("
"))
PushDynamicContent(string("QueryString"))
PushDynamicContent(string("
"))
PushDynamicContent(string("n1="))
temp := Request.__GET(String("n1"))
'PST.Str(string("**************************", Request#CR))
'PST.Str(string("__GET(String('n1'): "))
'PST.str(temp)
'PST.Str(string(Request#CR))
'PST.Str(string("**************************", Request#CR))
PushDynamicContent(temp)
PushDynamicContent(string("
"))
PushDynamicContent(string("n2="))
temp := Request.__GET(String("n2"))
'PST.Str(string("**************************", Request#CR))
'PST.Str(string("__GET(String('n2'): "))
'PST.str(temp)
'PST.Str(string(Request#CR))
'PST.Str(string("**************************", Request#CR))
PushDynamicContent(temp)
PushDynamicContent(string("
"))
PushDynamicContent(string("
"))
return
PRI Login | temp, postBack
postBack := IsPostBack(Request.FileName)
PushDynamicContent(string(" Is postback? "))
if(postBack)
PushDynamicContent(string("True"))
else
PushDynamicContent(string("False"))
PushDynamicContent(string("
"))
if(postBack)
PushDynamicContent(string("You entered
"))
PushDynamicContent(string(""))
PushDynamicContent(string("username="))
temp := Request.__POST(String("username"))
PushDynamicContent(temp)
PushDynamicContent(string("
"))
PushDynamicContent(string("password="))
temp := Request.__POST(String("password"))
PushDynamicContent(temp)
PushDynamicContent(string("
"))
PushDynamicContent(string("
"))
PushDynamicContent(string(""))
PushDynamicContent(string("This is just an example of a post!
"))
PushDynamicContent(string("Your password is safe."))
PushDynamicContent(string("
"))
return
PRI Upload | temp, postBack, packetSize
postBack := IsPostBack(Request.FileName)
PST.Str(string("**************************", Request#CR))
PST.Str(string("Upload: "))
PST.dec(postBack)
PST.Str(string(Request#CR))
PST.Str(string("**************************", Request#CR))
PushDynamicContent(string(" Is postback? "))
if(postBack)
PushDynamicContent(string("True"))
else
PushDynamicContent(string("False"))
PushDynamicContent(string("
"))
if(postBack)
temp := Request.__POST(String("filename"))
bytefill(@data, 0, _bytebuffersize)
repeat 3
packetSize := W5100.rxTCP(0, @data)
'WriteLog(@data)
'pst.str(@data)
return
PRI PushDynamicContent(content)
' Write the content to memory
' and update the pointer
bytemove(dynamicContentPtr, content, strsize(content))
dynamicContentPtr := dynamicContentPtr + strsize(content)
return
{{ }}
PUB Communicate | i
Repeat
TxRx.waitstr(String("!v="), clkfreq*20)
repeat i from 0 to 8
RData[i] := TxRx.GetLong
repeat i from 0 to 8
TxRx.PutLong(303) 'SData[i])
''///////////////user_Input variable identifers///////////////
{{
0 = do nothing/default
1 = stop/land/powerdown
2 = start command/power up sequence/initialize motors
3 = hold position/hover
4 = CW
5 = CCW
6 = move forward
7 = move reverse
8 = move left
9 = move right
10 = increase altitude
11 = decrease altitude
12 = reserved
13 = run search pattern
}}
PUB Get_Heading | avgvalue
avgvalue := Heading
IF avgvalue > 350 OR avgvalue < 10
Bearing := string("North")
ELSEIF avgValue > 11 AND avgvalue < 31
Bearing := string("North-East")
ELSEIF avgValue > 32 AND avgvalue < 52
Bearing := string("East")
ELSEIF avgValue > 53 AND avgvalue < 73
Bearing := string("South-East")
ELSEIF avgValue > 74 AND avgvalue < 94
Bearing := string("South")
ELSEIF avgValue > 95 AND avgvalue < 115
Bearing := string("South-West")
ELSEIF avgValue > 116 AND avgvalue < 136
Bearing := string("West")
ELSEIF avgValue > 137 AND avgvalue < 157
Bearing := string("North-West")
ELSE
Bearing := string("Unknown")
return avgvalue
PRI SetupXML
SDCard.changeDirectory(@approot)
SDCard.deleteEntry(string("send.xml"))
SDCard.newFile(string("send.xml"))
WriteSend(string("", Request#CR))
WriteSend(string("", Request#CR))
WriteSend(string(" ", Request#CR))
WriteSend(string(" "))
WriteSend(Request.NumberToAsccii(numvar))
WriteSend(string("", Request#CR))
WriteSend(string(" "))
WriteSend(Request.NumberToAsccii(numvar))
WriteSend(string("", Request#CR))
WriteSend(string(" "))
WriteSend(Request.NumberToAsccii(numvar))
WriteSend(string("", Request#CR))
WriteSend(string(" "))
WriteSend(Request.NumberToAsccii(numvar))
WriteSend(string("", Request#CR))
WriteSend(string(" "))
WriteSend(Request.NumberToAsccii(numvar))
WriteSend(string("", Request#CR))
WriteSend(string(" "))
WriteSend(Request.NumberToAsccii(numvar))
WriteSend(string("", Request#CR))
WriteSend(string(" "))
WriteSend(Request.NumberToAsccii(numvar))
WriteSend(string("", Request#CR))
WriteSend(string(" "))
WriteSend(Request.NumberToAsccii(numvar))
WriteSend(string("", Request#CR))
WriteSend(string(" "))
WriteSend(Request.NumberToAsccii(numvar))
WriteSend(string("", Request#CR))
WriteSend(string(" "))
WriteSend(Request.NumberToAsccii(numvar))
WriteSend(string("", Request#CR))
WriteSend(string(" "))
WriteSend(Request.NumberToAsccii(numvar))
WriteSend(string("", Request#CR))
WriteSend(string(" "))
WriteSend(Request.NumberToAsccii(numvar))
WriteSend(string("", Request#CR))
WriteSend(string(" ", Request#CR))
WriteSend(string("", Request#CR))
'PauseMSec(200)
return
PRI WriteSend(dataPtr)
SDCard.openFile(string("send.xml"), "a")
SDCard.writeData(dataPtr, strsize(dataPtr))
SDCard.closeFile
PUB WriteMAC(idx, value)
if value > 255
return false
if idx > 5
return false
WriteConfigByte(_MAC, idx, value)
return true
PUB ReadMAC(idx)
if idx > 5
return false
return eeprom.getbytex(EE, idx+_MAC)
PUB WriteGateway(idx, value)
if value > 255
return false
if idx > 4
return false
WriteConfigByte(_Gateway, idx, value)
return true
PUB ReadGateway(idx)
if idx > 5
return false
return eeprom.getbytex(EE, idx+_Gateway)
PUB WriteSubnet(idx, value)
if value > 255
return false
if idx > 4
return false
WriteConfigByte(_Subnet, idx, value)
return true
PUB ReadSubnet(idx)
if idx > 4
return false
return eeprom.getbytex(EE, idx+_Subnet)
PUB WriteIP(idx, value)
if value > 255
return false
if idx > 4
return false
WriteConfigByte(_IP, idx, value)
return true
PUB ReadIP(idx)
if idx > 5
return false
return eeprom.getbytex(EE, idx+_IP)
PUB WritePort(value)
if value > 65535
return false
eeprom.putwordx(EE, _PORT, value)
return true
PUB ReadPort
return eeprom.getwordx(EE, _PORT)
PUB WriteDHCP(value)
if value > 255
return false
if value <> 0
value := 1
eeprom.putwordx(EE, _DHCP, value)
return true
PUB ReadDHCP
return eeprom.getbytex(EE, _DHCP)
PRI WriteConfigByte(addr, idx, value)
eeprom.putbytex(EE, idx+addr, value)
PRI IsPostBack(newfile)
{
pst.char(13)
pst.str(string("####################"))
pst.char(13)
pst.str(newfile)
pst.char(13)
pst.str(@lastfile)
pst.char(13)
pst.str(string("####################"))
pst.char(13)
}
'Compare the new file with what we have in the buffer
if(strcomp(@lastfile, newfile) & Request.GetMethod == Request#HTTP_POST)
return true
else
'Clear
bytefill(@lastFile, 0, 12)
'Copy the new file name to the buffer
bytemove(@lastfile, newfile, strsize(newfile))
return false
PRI SetMac(_firstOctet)
W5100.WriteMACaddress(true, _firstOctet)
return
PRI SetGateway(_firstOctet)
'Set the Gatway address and display it in the terminal
W5100.WriteGatewayAddress(true, _firstOctet)
return
PRI SetSubnet(_firstOctet)
'Set the Subnet address and display it in the terminal
W5100.WriteSubnetMask(true, _firstOctet)
return
PRI SetIP(_firstOctet)
'Set the IP address and display it in the terminal
W5100.WriteIPAddress(true, _firstOctet)
return
PRI StringSend(_socket, _dataPtr)
W5100.txTCP(0, _dataPtr, strsize(_dataPtr))
return
PRI SendChar(_socket, _dataPtr)
W5100.txTCP(0, _dataPtr, 1)
return
PRI SendChars(_socket, _dataPtr, _length)
W5100.txTCP(0, _dataPtr, _length)
return
PRI OpenSocketAgain
W5100.SocketOpen(0, W5100#_TCPPROTO, localSocket, destSocket, @destIP[0])
W5100.SocketTCPlisten(0)
return
PRI PauseMSec(Duration)
'' Pause execution for specified milliseconds.
'' This routine is based on the set clock frequency.
''
'' params: Duration = number of milliseconds to delay
'' return: none
waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)
return 'end of PauseMSec
'***************************************
DAT
'***************************************
approot byte "\", 0
'systemFolder byte "system", 0
'tempFolder byte "temp", 0
placeholder byte "", 0
tempFile byte "temp.htm", 0
S0_SR byte 0
SN_SR byte 0
lastFile byte $0[12], 0
clipboard byte $0[1024], 0
dynamicContent byte $0[1024], 0
{{
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ 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. │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
}}
{{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}}
''**************************************
''
'' WIZnet W5100 Indirect Driver Ver. 00.6
''
'' Timothy D. Swieter, P.E.
'' Brilldea - purveyor of prototyping goods
'' www.brilldea.com
''
'' Generously funded by Parallax
'' www.parallax.com
''
'' Copyright (c) 2010 Timothy D. Swieter, P.E.
'' See end of file for terms of use and MIT License
''
'' Updated: June 26, 2010
''
'' Original file Brilldea_W5100_Indirect_Driver_Ver006.spin -
'' added to version control repository on January 5, 2011
''
''Description:
''
'' This is an Indirect/parallel Assembly language driver for the W5100.
'' This driver requires /RESET, /WR, /RD, /CS, 8x data and 2x address lines.
'' The SEN singal should be tied low to disable SPI mode. The /INT signal is
'' not employed in this version of the driver.
''
'' The functions are mostly implemented in ASM for very fast access. There is high level access
'' to the Indirect/parallel bus, but going through SPIN to do many of the functions adds considerable time.
''
'' One thing to note about this driver is the assumption is makes regarding the pins used for exchanging data
'' with the W5100. When starting the driver you provide the function with the first data pin - that is Data0.
'' The driver then assumes that all data pins are consecuitive and increasing. So if you define P0 as Data0, then
'' P2 is Data1....P7 is Data7.
''
'' In the program that calls this driver you will need to set up variables for such as the following:
''
'' 'Variables to hold the address configuration information as set
'' byte myMAC[6] '6 element array contianing MAC or source hardware address ex. "02:00:00:01:23:45"
'' byte myGateway[4] '4 element array containing gateway address ex. "192.168.0.1"
'' byte mySubnet[4] '4 element array contianing subnet mask ex. "255.255.255.0"
'' byte myIP[4] '4 element array containing IP address ex. "192.168.0.13"
''
''reference:
'' W5100_Data Sheet
''
'' 2711015Eady.pdf (Circuit Cellar Magazine Article: http://www.circuitcellar.com/archives/viewable/Eady207/2711015Eady.pdf)
'' Hydra EtherX Documentation
''
'' Programming and Customizing the multicore Propeller Microcontroller by various
'' TCP/IP Application Layer Protocols for Embedded Systems by M. Tim Jones
'' Networking and Internetworking with Microcontrollers by Fred Eady
''
'' Initial indirect driver coding done by Patrick von den Driesch (patrick1ab on Parallax's Forum)
''
''To do:
''
'' -in tx/rxUDP there are referneces to $0100 and $0800. Review if any of these should be made to variables or constants for better readability or future expansion
'' -test i variable initializing at start of tx/rxUDP is needed
'' -see if there is a command parameters that aren't being used - if so remove some of the longs located at the end of the ASM that are read from byte ram on each command execution
'' -add ability to change socket memory size.
''
''Revision Notes:
'' 0.5 ....start of fork from SPI code
'' 0.6 Code enhancements and optimizing
'' Fixed problem in UDP buffer losing data - thanks to Marko! Problem was in txUDP.
''
''**************************************
CON 'Constants to be located here
'***************************************
'***************************************
' Firmware Version
'***************************************
FWmajor = 0
FWminor = 6
DAT
TxtFWdate byte "June 26, 2010",0
CON
'***************************************
' System Definitions
'***************************************
_OUTPUT = 1 'Sets pin to output in DIRA register
_INPUT = 0 'Sets pin to input in DIRA register
_HIGH = 1 'High=ON=1=3.3V DC
_ON = 1
_LOW = 0 'Low=OFF=0=0V DC
_OFF = 0
_ENABLE = 1 'Enable (turn on) function/mode
_DISABLE = 0 'Disable (turn off) function/mode
'***************************************
' W5100 Common Register Definitions
'***************************************
_MR = $0000 'Mode Register
_GAR0 = $0001 'Gateway Address Register
_GAR1 = $0002
_GAR2 = $0003
_GAR3 = $0004
_SUBR0 = $0005 'Subnet Mask Address Register
_SUBR1 = $0006
_SUBR2 = $0007
_SUBR3 = $0008
_SHAR0 = $0009 'Source Hardware Address Register (MAC)
_SHAR1 = $000A
_SHAR2 = $000B
_SHAR3 = $000C
_SHAR4 = $000D
_SHAR5 = $000E
_SIPR0 = $000F 'Source IP Address Register
_SIPR1 = $0010
_SIPR2 = $0011
_SIPR3 = $0012
'Reserved space $0013 - $0014
_IR = $0015 'Interrupt Register
_IMR = $0016 'Interrupt Mask Register
_RTR0 = $0017 'Retry Time Register
_RTR1 = $0018
_RCR = $0019 'Retry Count Register
_RMSR = $001A 'Rx Memory Size Register
_TMSR = $001B 'Tx Memory Size Register
_PATR0 = $001C 'Authentication Type in PPPoE Register
_PATR1 = $001D
'Reserved space $001E - $0027
_PTIMER = $0028 'PPP LCP Request Timer
_PMAGIC = $0029 'PPP LCP Magic Number
_UIPR0 = $002A 'Unreachable IP Address Register
_UIPR1 = $002B
_UIPR2 = $002C
_UIPR3 = $002D
_UPORT0 = $002E 'Unreachable Port Register
_UPORT1 = $002F
'Reserved space $0030 - $03FF
'***************************************
' W5100 Socket 0 Register Definitions
'***************************************
_S0_MR = $0400 'Socket 0 Mode Register
_S0_CR = $0401 'Socket 0 Command Register
_S0_IR = $0402 'Socket 0 Interrupt Register
_S0_SR = $0403 'Socket 0 Status Register
_S0_PORT0 = $0404 'Socket 0 Source Port Register
_S0_PORT1 = $0405
_S0_DHAR0 = $0406 'Socket 0 Destination Hardware Address Register
_S0_DHAR1 = $0407
_S0_DHAR2 = $0408
_S0_DHAR3 = $0409
_S0_DHAR4 = $040A
_S0_DHAR5 = $040B
_S0_DIPR0 = $040C 'Socket 0 Destination IP Address Register
_S0_DIPR1 = $040D
_S0_DIPR2 = $040E
_S0_DIPR3 = $040F
_S0_DPORT0 = $0410 'Socket 0 Destination Port Register
_S0_DPORT1 = $0411
_S0_MSSR0 = $0412 'Socket 0 Maximum Segment Size Register
_S0_MSSR1 = $0413
_S0_PROTO = $0414 'Socket 0 Protocol in IP Raw Mode Register
_S0_TOS = $0415 'Socket 0 IP TOS Register
_S0_TTL = $0416 'Socket 0 IP TTL Register
'Reserved space $0417 - $041F
_S0_TX_FSRO = $0420 'Socket 0 TX Free Size Register
_S0_TX_FSR1 = $0421
_S0_TX_RD0 = $0422 'Socket 0 TX Read Pointer Register
_S0_TX_RD1 = $0423
_S0_TX_WR0 = $0424 'Socket 0 TX Write Pointer Register
_S0_TX_WR1 = $0425
_S0_RX_RSR0 = $0426 'Socket 0 RX Received Size Register
_S0_RX_RSR1 = $0427
_S0_RX_RD0 = $0428 'Socket 0 RX Read Pointer Register
_S0_RX_RD1 = $0429
'Reserved space $042A - $04FF
'***************************************
' W5100 Socket 1 Register Definitions
'***************************************
_S1_MR = $0500 'Socket 1 Mode Register
_S1_CR = $0501 'Socket 1 Command Register
_S1_IR = $0502 'Socket 1 Interrupt Register
_S1_SR = $0503 'Socket 1 Status Register
_S1_PORT0 = $0504 'Socket 1 Source Port Register
_S1_PORT1 = $0505
_S1_DHAR0 = $0506 'Socket 1 Destination Hardware Address Register
_S1_DHAR1 = $0507
_S1_DHAR2 = $0508
_S1_DHAR3 = $0509
_S1_DHAR4 = $050A
_S1_DHAR5 = $050B
_S1_DIPR0 = $050C 'Socket 1 Destination IP Address Register
_S1_DIPR1 = $050D
_S1_DIPR2 = $050E
_S1_DIPR3 = $050F
_S1_DPORT0 = $0510 'Socket 1 Destination Port Register
_S1_DPORT1 = $0511
_S1_MSSR0 = $0512 'Socket 1 Maximum Segment Size Register
_S1_MSSR1 = $0513
_S1_PROTO = $0514 'Socket 1 Protocol in IP Raw Mode Register
_S1_TOS = $0515 'Socket 1 IP TOS Register
_S1_TTL = $0516 'Socket 1 IP TTL Register
'Reserved space $0517 - $051F
_S1_TX_FSRO = $0520 'Socket 1 TX Free Size Register
_S1_TX_FSR1 = $0521
_S1_TX_RD0 = $0522 'Socket 1 TX Read Pointer Register
_S1_TX_RD1 = $0523
_S1_TX_WR0 = $0524 'Socket 1 TX Write Pointer Register
_S1_TX_WR1 = $0525
_S1_RX_RSR0 = $0526 'Socket 1 RX Received Size Register
_S1_RX_RSR1 = $0527
_S1_RX_RD0 = $0528 'Socket 1 RX Read Pointer Register
_S1_RX_RD1 = $0529
'Reserved space $052A - $05FF
'***************************************
' W5100 Socket 2 Register Definitions
'***************************************
_S2_MR = $0600 'Socket 2 Mode Register
_S2_CR = $0601 'Socket 2 Command Register
_S2_IR = $0602 'Socket 2 Interrupt Register
_S2_SR = $0603 'Socket 2 Status Register
_S2_PORT0 = $0604 'Socket 2 Source Port Register
_S2_PORT1 = $0605
_S2_DHAR0 = $0606 'Socket 2 Destination Hardware Address Register
_S2_DHAR1 = $0607
_S2_DHAR2 = $0608
_S2_DHAR3 = $0609
_S2_DHAR4 = $060A
_S2_DHAR5 = $060B
_S2_DIPR0 = $060C 'Socket 2 Destination IP Address Register
_S2_DIPR1 = $060D
_S2_DIPR2 = $060E
_S2_DIPR3 = $060F
_S2_DPORT0 = $0610 'Socket 2 Destination Port Register
_S2_DPORT1 = $0611
_S2_MSSR0 = $0612 'Socket 2 Maximum Segment Size Register
_S2_MSSR1 = $0613
_S2_PROTO = $0614 'Socket 2 Protocol in IP Raw Mode Register
_S2_TOS = $0615 'Socket 2 IP TOS Register
_S2_TTL = $0616 'Socket 2 IP TTL Register
'Reserved space $0617 - $061F
_S2_TX_FSRO = $0620 'Socket 2 TX Free Size Register
_S2_TX_FSR1 = $0621
_S2_TX_RD0 = $0622 'Socket 2 TX Read Pointer Register
_S2_TX_RD1 = $0623
_S2_TX_WR0 = $0624 'Socket 2 TX Write Pointer Register
_S2_TX_WR1 = $0625
_S2_RX_RSR0 = $0626 'Socket 2 RX Received Size Register
_S2_RX_RSR1 = $0627
_S2_RX_RD0 = $0628 'Socket 2 RX Read Pointer Register
_S2_RX_RD1 = $0629
'Reserved space $062A - $06FF
'***************************************
' W5100 Socket 3 Register Definitions
'***************************************
_S3_MR = $0700 'Socket 3 Mode Register
_S3_CR = $0701 'Socket 3 Command Register
_S3_IR = $0702 'Socket 3 Interrupt Register
_S3_SR = $0703 'Socket 3 Status Register
_S3_PORT0 = $0704 'Socket 3 Source Port Register
_S3_PORT1 = $0705
_S3_DHAR0 = $0706 'Socket 3 Destination Hardware Address Register
_S3_DHAR1 = $0707
_S3_DHAR2 = $0708
_S3_DHAR3 = $0709
_S3_DHAR4 = $070A
_S3_DHAR5 = $070B
_S3_DIPR0 = $070C 'Socket 3 Destination IP Address Register
_S3_DIPR1 = $070D
_S3_DIPR2 = $070E
_S3_DIPR3 = $070F
_S3_DPORT0 = $0710 'Socket 3 Destination Port Register
_S3_DPORT1 = $0711
_S3_MSSR0 = $0712 'Socket 3 Maximum Segment Size Register
_S3_MSSR1 = $0713
_S3_PROTO = $0714 'Socket 3 Protocol in IP Raw Mode Register
_S3_TOS = $0715 'Socket 3 IP TOS Register
_S3_TTL = $0716 'Socket 3 IP TTL Register
'Reserved space $0717 - $071F
_S3_TX_FSRO = $0720 'Socket 3 TX Free Size Register
_S3_TX_FSR1 = $0721
_S3_TX_RD0 = $0722 'Socket 3 TX Read Pointer Register
_S3_TX_RD1 = $0723
_S3_TX_WR0 = $0724 'Socket 3 TX Write Pointer Register
_S3_TX_WR1 = $0725
_S3_RX_RSR0 = $0726 'Socket 3 RX Received Size Register
_S3_RX_RSR1 = $0727
_S3_RX_RD0 = $0728 'Socket 3 RX Read Pointer Register
_S3_RX_RD1 = $0729
'Reserved space $072A - $07FF
'***************************************
' W5100 Register Masks & Values Defintions
'***************************************
'Used in the mode register (MR)
_RSTMODE = %1000_0000 'If 1, internal registers are initialized
_PBMODE = %0001_0000 'Ping block mode, 1 is enabled
_PPPOEMODE = %0000_1000 'PPPoE mode, 1 is enabled
_AIMODE = %0000_0010 'Address auto-increment mode
_INDMODE = %0000_0001 'Indirect bus mode
'Used in the Interrupt Register (IR) & Interrupt Mask Register (IMR)
_CONFLICTM = %1000_0000
_UNREACHM = %0100_0000
_PPPoEM = %0010_0000
_S3_INTM = %0000_1000 'Socket 3 interrupt bit mask (1 = interrupt)
_S2_INTM = %0000_0100 'Socket 2 interrupt bit mask (1 = interrupt)
_S1_INTM = %0000_0010 'Socket 1 interrupt bit mask (1 = interrupt)
_S0_INTM = %0000_0001 'Socket 0 interrupt bit mask (1 = interrupt)
'Used in the RX memory size register(RMSR)
_S3_SM = %1100_0000 'Socket 3 size mask
_S2_SM = %0011_0000 'Socket 2 size mask
_S1_SM = %0000_1100 'Socket 1 size mask
_S0_SM = %0000_0011 'Socket 0 size mask
_1KB = %00 '1KB memory size
_2KB = %01 '2KB memory size
_4KB = %10 '4KB memory size
_8KB = %11 '8KB memory size
'Used in the socket n mode register (Sn_MR)
_MULTIM = %1000_0000 'Enable/disable multicasting in UDP
_PROTOCOLM = %0000_1111 'Registers for setting protocol
_CLOSEDPROTO = %0000 'Closed
_TCPPROTO = %0001 'TCP
_UDPPROTO = %0010 'UDP
_IPRAWPROTO = %0011 'IPRAW
_MACRAW = %0100 'MACRAW (used in socket 0)
_PPPOEPROTO = %0101 'PPPoE (used in socket 0)
'Used in the socket n command register (Sn_CR)
_OPEN = $01 'Initialize a socket
_LISTEN = $02 'In TCP mode, waits for request from client
_CONNECT = $04 'In TCP mode, sends connect request to server
_DISCON = $08 'In TCP mode, request to disconnect
_CLOSE = $10 'Closes socket
_SEND = $20 'Transmits data
_SEND_MAC = $21 'In UDP mode, like send, but uses MAC
_SEND_KEEP = $22 'In TCP mode, check connection status by sending 1 byte
_RECV = $40 'Receiving is processed
'Used in socket n interrupt register (Sn_IR)
_SEND_OKM = %0001_0000 'Set to 1 if send operation is completed
_TIMEOUTM = %0000_1000 'Set to 1 if timeout occured during transmission
_RECVM = %0000_0100 'Set to 1 if data is received
_DISCONM = %0000_0010 'Set to 1 if connection termination is requested
_CONM = %0000_0001 'Set to 1 if connection is established
'Used in socket n status register (Sn_SR)
_SOCK_CLOSED = $00
_SOCK_INIT = $13
_SOCK_LISTEN = $14
_SOCK_SYNSENT = $15
_SOCK_SYNRECV = $16
_SOCK_ESTAB = $17
_SOCK_FIN_WAIT= $18
_SOCK_CLOSING = $1A
_SOCK_TIME_WAIT=$1B
_SOCK_LACK_ACK =$1D
_SOCK_CLOSE_WT= $1C
_SOCK_UDP = $22
_SOCK_IPRAW = $32
_SOCK_MACRAW = $42
_SOCK_PPPOE = $5F
_SOCK_ARP1 = $11
_SOCK_ARP2 = $21
_SOCK_ARP3 = $31
'SPI OP-code when assembly packet to read/write to W5100
_WRITEOP = $F0 'Signals a write operation in SPI mode
_READOP = $0F 'Signals a read operation in SPI mode
'RX & TX definitions
_TX_base = $4000 'Base address of TX buffer
_RX_base = $6000 'Base address of RX buffer
_TX_mask = $7FF 'Mask for default 2K buffer for each socket
_RX_mask = $7FF 'Mask for default 2K buffer for each socket
_UDP_header = 8 '8 bytes of data in the UDP header from the W5100
'***************************************
' Command Definitions for ASM W5100 Indirect/parallel Routine
'***************************************
'_reserved = 0 'This is the default state - means ASM is waiting for command
_readIND = 1 << 16 'High level access to reading from the W5100 via indirect/parallel
_writeIND = 2 << 16 'High level access to writing to the W5100 via indirect/parallel
_SetMAC = 3 << 16 'Set the MAC ID in the W5100
_SetGateway = 4 << 16 'Set the gateway address in the W5100
_SetSubnet = 5 << 16 'Set the subnet address in the W5100
_SetIP = 6 << 16 'Set the IP address in the W5100
_ReadMAC = 7 << 16 'Recall the MAC ID in the W5100
_ReadGateway = 8 << 16 'Recall the gateway address in the W5100
_ReadSubnet = 9 << 16 'Recall the subnet address in the W5100
_ReadIP = 10 << 16 'Recall the IP address in the W5100
_PingBlock = 11 << 16 'Enable/disable ping response
_rstHW = 12 << 16 'Reset the W5100 IC via hardware
_rstSW = 13 << 16 'Reset the W5100 IC via hardware
_Sopen = 14 << 16 'Open a socket
_Sdiscon = 15 << 16 'Disconnect a socket
_Sclose = 16 << 16 'Close a socket
_lastCmd = 17 << 16 'Place holder for last command
'***************************************
' Driver Flag Definitions
'***************************************
_Flag_ASMstarted = |< 1 'Flag to indicated asm routine is started succesfully
'**************************************
VAR 'Variables to be located here
'***************************************
'processor overhead
long W5100cog 'Cog ID
long W5100flags 'Flags for status
'Command setup
long command 'stores command and arguments for the ASM driver
'W5100 Indirect/Parallel pin definitions
'Pins/masks are actually setup in SPIN by modifying the memory that is copied into the cog. This saves space.
'The pins/masks are defined in the dat section. The definitions include the following pins:
'Address[0] - output
'Address[1] - output
'Chip Select - active low, output
'Read cmd - active low, output
'Write cmd - active low, output
'Reset - active low, output
'Data[0] to [7] - output/input
'SPI Enable - output
'***************************************
OBJ 'Object declaration to be located here
'***************************************
'none
'~~~~~~~~~~~~~~~~~~~~~~~~~~Start/Stop Routines~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'***************************************
PUB StopINDIRECT
'***************************************
'' Stop the W5100 Indirect/parallel Driver cog if one is running.
'' Only a single cog can be running at a time.
'' This routine will free a cog.
''
'' params: none
'' return: none
if W5100cog 'Is cog non-zero?
cogstop(W5100cog~ - 1) 'Yes, stop the cog and then make value zero
'Reset any data held by the driver
W5100flags := 0 'Clear all the flags
ADD0mask := 0 'Clear all masks
ADD1mask := 0
CSmask := 0
RDmask := 0
WRmask := 0
RESETmask := 0
DATAmask := 0
SENmask := 0
WRCSmask := 0
RDCSmask := 0
WRRDCSmask := 0
return 'end of StopINDIRECT
'***************************************
PUB StartINDIRECT(_databit0, _addr0, _addr1, _cs, _rd, _wr, _reset, _sen) : okay
'***************************************
'' Initializes the I/O and registers based on parameters.
'' After initilization another cog is started which is the
'' cog responsible for the Indirect/parallel communication to the W5100.
''
'' The W5100 Indirect/parallel cog will allow only one instance of itself
'' to run and the it consumes only 1 cog.
''
'' params: the seven pins of required for indirect/parallel
'' _databit0 is the pin for bit0, the other pins are assumed to be sequential following this pin.
'' _sen = the pin of the W5100 SPI_EN. If not used, set to -1
'' return: value of cog if started or zero if not started
'Keeps from two cogs running
StopINDIRECT
'Initialize the I/O for writing the mask data to the memory area that will be copied into a COG.
'This routine assumes Indirect/parallel connection, SPI_EN will be made output low by the ASM cog
ADD0mask := |< _addr0
ADD1mask := |< _addr1
CSmask := |< _cs
RDmask := |< _rd
WRmask := |< _wr
RESETmask := |< _reset
BASEpin := _databit0 'The base pin of data byte for shifting operations
DATAmask := %1111_1111 << _databit0 'Special mask setup with eight pins
WRCSmask := WRmask | CSmask
RDCSmask := RDmask | CSmask
WRRDCSmask:= WRmask | RDmask | CSmask
if _sen <> -1
SENmask := |< _sen
else
SENmask := 0
'Clear the command buffer - be sure no commands were set before initializing
command := 0
'Start a cog to execute the ASM routine
okay := W5100cog := cognew(@Entry, @command) + 1
'Set a flag if cog started succesfully
if okay
W5100flags := _Flag_ASMstarted
return 'end of StartINDIRECT
'~~~~~~~~~~~~~~~~~~~~~~~~~~Command Routines~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'***************************************
PUB InitAddresses( _block, _macPTR, _gatewayPTR, _subnetPTR, _ipPTR)
'***************************************
'' Initialize all four addresses.
''
'' params: _block if true will wait for ASM routine to send before returning from this function
'' _mac, _gateway, _subnet, _ip are pointers to appropriate size byte arrays
'' return: none
'Checks on if the ASM cog is running is done in each of the following routines
WriteMACaddress(_block, _macPTR)
WriteGatewayAddress(_block, _gatewayPTR)
WriteSubnetMask(_block, _subnetPTR)
WriteIPaddress(_block, _ipPTR)
return 'end of InitAddresses
'***************************************
PUB WriteMACaddress( _block, _macPTR)
'***************************************
'' Write the specified MAC address to the W5100.
''
'' params: _block if true will wait for ASM routine to send before continuing
'' The pointer should point to a 6 byte array.
'' byte[0] = highest octet and byte[5] = lowest octet
'' example 02:00:00:01:23:45 where byte[0] = $02 and byte[5] = $45
''
'' return: none
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Send the command
command := _SetMAC + _macPTR
'wait for the command to complete or just move on
if _block
repeat while command
return 'end of WriteMACaddress
'***************************************
PUB WriteGatewayAddress(_block, _gatewayPTR)
'***************************************
'' Write the specified gateway address to the W5100.
''
'' params: _block if true will wait for ASM routine to send before continuing
'' The pointer should point to a 4 byte array.
'' byte[0] = highest octet and byte[3] = lowest octet
'' example 192.168.0.1 where byte[0] = 192 and byte[3] = 1
''
'' return: none
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Send the command
command := _SetGateway + _gatewayPTR
'wait for the command to complete or just move on
if _block
repeat while command
return 'end of WriteGatewayAddress
'***************************************
PUB WriteSubnetMask(_block, _subnetPTR)
'***************************************
'' Write the specified Subnet mask to the W5100.
''
'' params: _block if true will wait for ASM routine to send before continuing
'' The pointer should point to a 4 byte array.
'' byte[0] = highest octet and byte[3] = lowest octet
'' example 255.255.255.0 where byte[0] = 255 and byte[3] = 0
''
'' return: none
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Send the command
command := _SetSubnet + _subnetPTR
'wait for the command to complete or just move on
if _block
repeat while command
return 'end of WriteSubnetMask
'***************************************
PUB WriteIPaddress(_block, _ipPTR)
'***************************************
'' Write the specified IP address to the W5100.
''
'' params: _block if true will wait for ASM routine to send before continuing
'' The pointer should point to a 4 byte array.
'' byte[0] = highest octet and byte[3] = lowest octet
'' example 192.168.0.13 where byte[0] = 192 and byte[3] = 13
''
'' return: none
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Send the command
command := _SetIP + _ipPTR
'wait for the command to complete or just move on
if _block
repeat while command
return 'end of WriteIPaddress
'***************************************
PUB ReadMACaddress(_macPTR)
'***************************************
'' Read the MAC address from the W5100.
''
'' params: none
'' return: The pointer should point to a 6 byte array.
'' byte[0] = highest octet and byte[5] = lowest octet
'' example 02:00:00:01:23:45 where byte[0] = $02 and byte[5] = $45
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Send the command
command := _ReadMAC + _macPTR
'wait for the command to complete
repeat while command
return 'end of ReadMACaddress
'***************************************
PUB ReadGatewayAddress(_gatewayPTR)
'***************************************
'' Read the gateway address from the W5100.
''
'' params: none
'' return: The pointer should point to a 4 byte array.
'' byte[0] = highest octet and byte[3] = lowest octet
'' example 192.168.0.1 where byte[0] = 192 and byte[3] = 1
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Send the command
command := _ReadGateway + _gatewayPTR
'wait for the command to complete
repeat while command
return 'end of ReadGatewayAddress
'***************************************
PUB ReadSubnetMask(_subnetPTR)
'***************************************
'' Read the specified Subnet mask from the W5100
''
'' params: none
'' return: The pointer should point to a 4 byte array.
'' byte[0] = highest octet and byte[3] = lowest octet
'' example 255.255.255.0 where byte[0] = 255 and byte[3] = 0
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Send the command
command := _ReadSubnet + _subnetPTR
'wait for the command to complete
repeat while command
return 'end of ReadSubnetMask
'***************************************
PUB ReadIPaddress(_ipPTR)
'***************************************
'' Read the specified IP address from the W5100
''
'' params: none
'' return: The pointer should point to a 4 byte array.
'' byte[0] = highest octet and byte[3] = lowest octet
'' example 192.168.0.13 where byte[0] = 192 and byte[3] = 13
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Send the command
command := _ReadIP + _ipPTR
'wait for the command to complete
repeat while command
return 'end of ReadIPaddress
'***************************************
PUB PingBlock(_block, _bool)
'***************************************
'' Enable/disable if the W5100 responds to pings.
''
'' params: _block if true will wait for ASM routine to send before continuing
'' _bool is a bool, true is W5100 will NOT respond, false W5100 will respond
'' return: none
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Send the command
command := _pingBlock + @_bool
'wait for the command to complete or just move on
if _block
repeat while command
return 'end of PingBlock
'***************************************
PUB ResetHardware(_block)
'***************************************
'' Reset the W5100 via hardware
''
'' params: _block if true will wait for ASM routine to send before continuing
'' return: none
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Send the command
command := _rstHW
'wait for the command to complete or just move on
if _block
repeat while command
return 'end of ResetHardware
'***************************************
PUB ResetSoftware(_block)
'***************************************
'' Reset the W5100 via software
''
'' params: _block if true will wait for ASM routine to send before continuing
'' return: none
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Send the command
command := _rstSW
'wait for the command to complete or just move on
if _block
repeat while command
return 'end of ResetSoftware
'***************************************
PUB SocketOpen(_socket, _mode, _srcPort, _destPort, _destIP)
'***************************************
'' Open the specified socket in the specified mode on the W5100.
'' The mode can be either TCP or UDP.
''
'' params: _socket is a value of 0 to 3 - only four sockets on the W5100
'' _mode is one of the constants specifing closed, TCP, UDP, IPRaw etc
'' _srcPort, _destPort are the ports to use in the connection pass by value
'' _destIP is a pointer to the destination IP byte array (use the @on the variable)
'' return: none
'maybe add validation of data up here in spin as oppose to ASM??
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Send the command
command := _Sopen + @_socket
'wait for the command to complete
repeat while command
return 'end of SocketOpen
'***************************************
PUB SocketTCPlisten(_socket) | temp0
'***************************************
'' Check if a socket is TCP and open and if so then set the socket to listen on the W5100
''
'' params: _socket is a value of 0 to 3 - only four sockets on the W5100
'' return: none
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Check if the socket is TCP and open by looking at socket status register
readIND((_S0_SR + (_socket * $0100)), @temp0, 1)
if temp0.byte[0] <> _SOCK_INIT
return
'Tell the W5100 to listen on the particular socket
temp0 := _LISTEN
writeIND(true, (_S0_CR + (_socket * $0100)), @temp0, 1)
return 'end of SocketTCPlisten
'***************************************
PUB SocketTCPconnect(_socket) | temp0
'***************************************
'' Check if a socket is TCP and open and if so then set the socket to connect on the W5100
''
'' params: _socket is a value of 0 to 3 - only four sockets on the W5100
'' return: none
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Check if the socket is TCP and open by looking at socket status register
readIND((_S0_SR + (_socket * $0100)), @temp0, 1)
if temp0.byte[0] <> _SOCK_INIT
return
'Tell the W5100 to connect to a particular socket
temp0 := _CONNECT
writeIND(true, (_S0_CR + (_socket * $0100)), @temp0, 1)
return 'end of SocketTCPconnect
'***************************************
PUB SocketTCPestablished(_socket) | temp0
'***************************************
'' Check if a socket has established a TCP connection
''
'' params: _socket is a value of 0 to 3 - only four sockets on the W5100
'' return: True if established, false if not
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Check if the socket is established or not
readIND((_S0_SR + (_socket * $0100)), @temp0, 1)
if temp0.byte[0] <> _SOCK_ESTAB
return false
else
return true
return false 'end of SocketTCPestablished
'***************************************
PUB SocketTCPdisconnect(_socket)
'***************************************
'' Disconnects the specified socket on the W5100.
''
'' params: _socket is a value of 0 to 3 - only four sockets on the W5100
'' return: none
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Send the command
command := _Sdiscon + @_socket
'wait for the command to complete
repeat while command
return 'end of SocketTCPdisconnect
'***************************************
PUB SocketClose(_socket)
'***************************************
'' Closes the specified socket on the W5100.
''
'' params: _socket is a value of 0 to 3 - only four sockets on the W5100
'' return: none
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Send the command
command := _Sclose + @_socket
'wait for the command to complete
repeat while command
return 'end of SocketClose
'***************************************
PUB rxTCP(_socket, _dataPtr) | temp0, RSR, pcktptr, pcktoffset, pcktstart, rolloverpoint
'***************************************
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Check if there is data to receive from the W5100
readIND((_S0_RX_RSR0 + (_socket * $0100)), @temp0, 2)
RSR.byte[1] := temp0.byte[0]
RSR.byte[0] := temp0.byte[1]
'Bring over the data if there is data
if RSR.word[0] <> 0
'Determine the offset and location to read data from in the W5100
readIND((_S0_RX_RD0 + (_socket * $0100)), @temp0, 2)
pcktptr.byte[1] := temp0.byte[0]
pcktptr.byte[0] := temp0.byte[1]
pcktoffset := pcktptr & _RX_mask
pcktstart := (_RX_base + (_socket * $0800)) + pcktoffset
'Read the data of the packet
if (pcktoffset + RSR.word[0]) > constant(_RX_mask + 1)
'process the data in two parts because the buffers rolls over
rolloverpoint := constant(_RX_mask + 1) - pcktoffset
readIND(pcktstart, _dataPtr, rolloverpoint)
pcktstart := (_RX_base + (_socket * $0800))
readIND(pcktstart, (_dataPtr + rolloverpoint), (RSR.word[0] - rolloverpoint))
else
'process the data in one part
readIND(pcktstart, _dataPtr, RSR.word[0])
'Update the W5100 registers, the packet pointer
temp0 := (pcktptr + RSR.word[0])
pcktptr.byte[1] := temp0.byte[0]
pcktptr.byte[0] := temp0.byte[1]
writeIND(true, (_S0_RX_RD0 + (_socket * $0100)), @pcktptr, 2)
'Tell the W5100 we received a packet
temp0 := _RECV
writeIND(true, (_S0_CR + (_socket * $0100)), @temp0, 1)
return RSR
return 0 'end of rxTCP
'***************************************
PUB txTCP(_socket, _dataPtr, _size) | temp0, freespace, pcktptr, pcktoffset, pcktstart, rolloverpoint
'***************************************
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Initialize
freespace := 0
'wait for room in the W5100 to send data
repeat until (freespace.word[0] > _size)
readIND((_S0_TX_FSRO + (_socket * $0100)), @temp0, 2)
freespace.byte[1] := temp0.byte[0]
freespace.byte[0] := temp0.byte[1]
'Get the place where to start writing the packet in the W5100
readIND((_S0_TX_WR0 + (_socket * $0100)), @temp0, 2)
pcktptr.byte[1] := temp0.byte[0]
pcktptr.byte[0] := temp0.byte[1]
pcktoffset := pcktptr & _TX_mask
pcktstart := (_TX_base + (_socket * $0800)) + pcktoffset
'Write the data based on rolling over in the buffer or not
if (pcktoffset + _size) > constant(_TX_mask + 1)
'process the data in two parts because the buffers rolls over
rolloverpoint := constant(_TX_mask + 1) - pcktoffset
writeIND(true, pcktstart, _dataPtr, rolloverpoint)
pcktstart := (_TX_base + (_socket * $0800))
writeIND(true, pcktstart, (_dataPtr + rolloverpoint), (_size - rolloverpoint))
else
'process the data in one part
writeIND(true, pcktstart, _dataPtr, _size)
'Calculate the packet pointer for the next go around and save it
temp0 := (pcktptr + _size)
pcktptr.byte[1] := temp0.byte[0]
pcktptr.byte[0] := temp0.byte[1]
writeIND(true, (_S0_TX_WR0 + (_socket * $0100)), @pcktptr, 2)
'Tell the W5100 to send the packet
temp0 := _SEND
writeIND(true, (_S0_CR + (_socket * $0100)), @temp0, 1)
return true
return false 'end of txTCP
'***************************************
PUB rxUDP(_socket, _dataPtr) | temp0, RSR, pcktsize, pcktptr, pcktoffset, pcktstart, rolloverpoint
'***************************************
'' Receive UDP data on the specified socket. Most of the heavy lifting of receiving data is handled by the ASM routine,
'' but for effeciency in coding the SPIN routine walks through the process of receiving data such as verifying and manipulating
'' the various register. This routine could be completely coded in ASM for faster operation.
''
'' The receive routine brings over only 1 packet of UDP data at a time. The packet is based on the size of data read in the packet
'' header and not the receive register size.
''
'' params: _socket is a value of 0 to 3 - only four sockets on the W5100
'' _dataPtr is a pointer to the byte array to be written to in HUBRAM (use the @ in front of the byte variable)
'' return: Non-zero value indicating bytes read from W5100 or zero if no data is read
''
'' The data returned is the complete packet as provided by the W5100. This means the following:
'' data[0]..[3] is the source IP address, data[4],[5] is the source port, data[6],[7] is the payload size and data[8] starts the payload
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Check if there is data to receive from the W5100
readIND((_S0_RX_RSR0 + (_socket * $0100)), @temp0, 2)
RSR.byte[1] := temp0.byte[0]
RSR.byte[0] := temp0.byte[1]
'Bring over the data if there is data
if RSR.word[0] <> 0
'Determine the offset and location to read data from in the W5100
readIND((_S0_RX_RD0 + (_socket * $0100)), @temp0, 2)
pcktptr.byte[1] := temp0.byte[0]
pcktptr.byte[0] := temp0.byte[1]
pcktoffset := pcktptr & _RX_mask
pcktstart := (_RX_base + (_socket * $0800)) + pcktoffset
'Read the header of the packet - the first 8 bytes
if (pcktoffset + _UDP_header) > constant(_RX_mask + 1)
'process the header in two parts because the buffers rolls over
rolloverpoint := constant(_RX_mask + 1) - pcktoffset
readIND(pcktstart, _dataPtr, rolloverpoint)
pcktstart := (_RX_base + (_socket * $0800))
readIND(pcktstart, (_dataPtr + rolloverpoint), (_UDP_header - rolloverpoint))
else
'process the header in one part
readIND(pcktstart, _dataPtr, _UDP_header)
'Get the size of the payload portion
pcktsize.byte[1] := byte[_dataPtr][6]
pcktsize.byte[0] := byte[_dataPtr][7]
pcktoffset := (pcktptr + _UDP_header) & _RX_mask
pcktstart := (_RX_base + (_socket * $0800)) + pcktoffset
_dataPtr += _UDP_header
'Read the data of the packet
if (pcktoffset + pcktsize.word[0]) > constant(_RX_mask + 1)
'process the data in two parts because the buffers rolls over
rolloverpoint := constant(_RX_mask + 1) - pcktoffset
readIND(pcktstart, _dataPtr, rolloverpoint)
pcktstart := (_RX_base + (_socket * $0800))
readIND(pcktstart, (_dataPtr + rolloverpoint), (pcktsize.word[0] - rolloverpoint))
else
'process the data in one part
readIND(pcktstart, _dataPtr, pcktsize.word[0])
'Update the W5100 registers, the packet pointer
temp0 := (pcktptr + _UDP_header + pcktsize.word[0])
pcktptr.byte[1] := temp0.byte[0]
pcktptr.byte[0] := temp0.byte[1]
writeIND(true, (_S0_RX_RD0 + (_socket * $0100)), @pcktptr, 2)
'Tell the W5100 we received a packet
temp0 := _RECV
writeIND(true, (_S0_CR + (_socket * $0100)), @temp0, 1)
return (pcktsize.word[0] + _UDP_header)
return 0 'end of rxUDP
'***************************************
PUB txUDP(_socket, _dataPtr) | temp0, payloadsize, freespace, pcktptr, pcktoffset, pcktstart, rolloverpoint
'***************************************
'' Transmit UDP data on the specified socket and port. Most of the heavy lifting of transmitting data is handled by the ASM routine,
'' but for effeciency in coding the SPIN routine walks through the process of transmitting data such as verifying and manipulating
'' the various register. This routine could be completely coded in ASM for faster operation.
''
'' The transmit routine sends only 1 packet of UDP data at a time. The packet is based on the size of data read in the packet
'' header. This routine waits for room in the W5100 to send the packet.
''
'' params: _socket is a value of 0 to 3 - only four sockets on the W5100
'' _dataPtr is a pointer to the byte(s) of data to read from HUBRAM and sent (use the @ in front of the byte variable)
'' return: True if data was put in W5100 and told to be sent, otherwise false
''
'' The data packet passed to this routine should be of the form of the following:
'' data[0]..[3] is the destination IP address, data[4],[5] is the destination port, data[6],[7] is the payload size and data[8] starts the payload
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Get the size of the packet to send, this doesn't include the header info
payloadsize := 0
freespace := 0
payloadsize.byte[1] := byte[_dataPtr][6] 'hi-byte
payloadsize.byte[0] := byte[_dataPtr][7] 'lo-byte
'wait for room in the W5100 to send data
repeat until (freespace.word[0] > payloadsize.word[0])
readIND((_S0_TX_FSRO + (_socket * $0100)), @temp0, 2)
freespace.byte[1] := temp0.byte[0]
freespace.byte[0] := temp0.byte[1]
'Tell the W5100 the destination address and destination socket
writeIND(true, (_S0_DIPR0 + (_socket * $0100)), _dataPtr, 6)
_dataPtr += _UDP_header
'Get the place where to start writing the packet in the W5100
readIND((_S0_TX_WR0 + (_socket * $0100)), @temp0, 2)
pcktptr.byte[1] := temp0.byte[0]
pcktptr.byte[0] := temp0.byte[1]
pcktoffset := pcktptr & _TX_mask
pcktstart := (_TX_base + (_socket * $0800)) + pcktoffset
'Write the data based on rolling over in the buffer or not
if (pcktoffset + payloadsize.word[0]) > constant(_TX_mask + 1)
'process the data in two parts because the buffers rolls over
rolloverpoint := constant(_TX_mask + 1) - pcktoffset
writeIND(true, pcktstart, _dataPtr, rolloverpoint)
pcktstart := (_TX_base + (_socket * $0800))
writeIND(true, pcktstart, (_dataPtr + rolloverpoint), payloadsize.word[0] - rolloverpoint)
else
'process the data in one part
writeIND(true, pcktstart, _dataPtr, payloadsize.word[0])
'Calculate the packet pointer for the next go around and save it
'Update the W5100 registers, the packet pointer
temp0 := (pcktptr + payloadsize.word[0])
pcktptr.byte[1] := temp0.byte[0]
pcktptr.byte[0] := temp0.byte[1]
writeIND(true, (_S0_TX_WR0 + (_socket * $0100)), @pcktptr, 2)
'Tell the W5100 to send the packet
temp0 := _SEND
writeIND(true, (_S0_CR + (_socket * $0100)), @temp0, 1)
return true
return false 'end of txUDP
'***************************************
PUB readIND(_register, _dataPtr, _Numbytes)
'***************************************
'' High level access to Indirect/parallel routine for reading from the W5100.
'' Note for faster execution of functions code them in assembly routine like the examples of setting the MAC/IP addresses.
''
'' params: _register is the 2 byte register address. See the constant block with register definitions
'' _dataPtr is the place to return the byte(s) of data read from the W5100 (use the @ in front of the byte variable)
'' _Numbytes is the number of bytes to read
'' return: none
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Send the command
command := _readIND + @_register
'wait for the command to complete
repeat while command
return 'end of readIND
'***************************************
PUB writeIND(_block, _register, _dataPtr, _Numbytes)
'***************************************
'' High level access to Indirect/parallel routine for writing to the W5100.
'' Note for faster execution of functions code them in assembly routine like the examples of setting the MAC/IP addresses.
''
'' params: _block if true will wait for ASM routine to send before continuing
'' _register is the 2 byte register address. See the constant block with register definitions
'' _dataPtr is a pointer to the byte(s) of data to be written (use the @ in front of the byte variable)
'' _Numbytes is the number of bytes to write
'' return: none
'If the ASM cog is running, execute the command
if (W5100flags & _Flag_ASMstarted)
'Send the command
command := _writeIND + @_register
'wait for the command to complete or just move on
if _block
repeat while command
return 'end of writeIND
'~~~~~~~~~~~~~~~~~~~~~~~~~~Utility Routines~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' none
'~~~~~~~~~~~~~~~~~~~~~~~~~~DAT~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'***************************************
DAT
'***************************************
'' Assembly language driver for W5100 Indirect/parallel
org
'-----------------------------------------------------------------------------------------------------
'Start of assembly routine
'-----------------------------------------------------------------------------------------------------
Entry
'Upon starting the ASM cog the first thing to do is set the I/O states and directions. SPIN already
'setup the masks for each pin in the defined data section of the routine before starting the COG.
'The following ASM routine makes some assumptions about I/O states. CS, RD and WR pins are always
'assumed to be high. If code asserts them low, then a code routine must return them high before
'exiting.
'Set the initial state of the I/O, unless listed here, the output is initialized as off/low
mov outa, CSmask 'W5100 Chip Select is initialized as high
or outa, RDmask 'W5100 Read cmd is initialized as high
or outa, WRmask 'W5100 Write cmd is initialized as high
'Remaining outputs initialized as low including reset
'NOTE: the W5100 is held in reset because the pin is low
'Next set up the I/O with the masks in the direction register
'all outputs pins are set up here because input is the default state
mov dira, ADD0mask 'Set to an output and clears cog dira register
or dira, ADD1mask 'Set to an output
or dira, CSmask 'Set to an output
or dira, RDmask 'Set to an output
or dira, WRmask 'Set to an output
or dira, RESETmask 'Set to an output
or dira, DATAmask 'Set to an output
or dira, SENmask 'Set to an output
or outa, RESETmask 'Finally - make the reset line high for the W5100 to come out of reset
mov t0, _UnrstTime 'Time to wait coming out of reset before proceeding
add t0, cnt 'Add in the current system counter
waitcnt t0, #0 'Wait
mov data, #%0000_0011 'For setting up MR with Indirect mode and autoincrement address function
call #wIndirectMR 'Write the values to the register
'-----------------------------------------------------------------------------------------------------
'Main loop
'wait for a command to come in and then process it.
'-----------------------------------------------------------------------------------------------------
CmdWait
rdlong cmdAdd, par wz 'Check for a command being present
if_z jmp #CmdWait 'If there is no command, jump to check again
mov t1, cmdAdd 'Take a copy of the command/address combo to work on
rdlong paramA, t1 'Get parameter A value
add t1, #4 'Increment the address pointer by four bytes
rdlong paramB, t1 'Get parameter B value
add t1, #4 'Increment the address pointer by four bytes
rdlong paramC, t1 'Get parameter C value
add t1, #4 'Increment the address pointer by four bytes
rdlong paramD, t1 'Get parameter D value
add t1, #4 'Increment the address pointer by four bytes
rdlong paramE, t1 'Get parameter E value
mov t0, cmdAdd 'Take a copy of the command/address combo to work on
shr t0, #16 wz 'Get the command
cmp t0, #(_lastCmd>>16)+1 wc 'Check for valid command
if_z_or_nc jmp #:CmdExit 'Command is invalid so exit loop
shl t0, #1 'Shift left, multiply by two
add t0, #:CmdTable-2 'add in the "call" address"
jmp t0 'Jump to the command
'The table of commands that can be called
:CmdTable call #rINDcmd 'Read a byte from the W5100 - high level call
jmp #:CmdExit
call #wINDcmd 'Write a byte to the W5100 - high level call
jmp #:CmdExit
call #wMAC 'Write the MAC ID
jmp #:CmdExit
call #wGateway 'Write the Gateway address
jmp #:CmdExit
call #wSubnet 'Write the Subnet address
jmp #:CmdExit
call #wIP 'Write the IP address
jmp #:CmdExit
call #rMAC 'Read the MAC ID
jmp #:CmdExit
call #rGateway 'Read the Gateway address
jmp #:CmdExit
call #rSubnet 'Read the Subnet address
jmp #:CmdExit
call #rIP 'Read the IP Address
jmp #:CmdExit
call #pingBlk 'Enable/disable a ping response
jmp #:CmdExit
call #rstHW 'Hardware reset of W5100
jmp #:CmdExit
call #rstSW 'Software reset of W5100
jmp #:CmdExit
call #sOPEN 'Open a socket
jmp #:CmdExit
call #sDISCON 'Disconnect a socket
jmp #:CmdExit
call #sCLOSE 'Close a socket
jmp #:CmdExit
call #LastCMD 'PlaceHolder for last command
jmp #:CmdExit
:CmdTableEnd
'End of processing a command
:CmdExit wrlong _zero, par 'Clear the command status
jmp #CmdWait 'Go back to waiting for a new command
'-----------------------------------------------------------------------------------------------------
'Command sub-routine to read a register from the W5100 - a high level call
'-----------------------------------------------------------------------------------------------------
rINDcmd
mov reg, paramA 'Move the register address into a variable for processing
mov ram, ParamB 'Move the address of the returned byte into a variable for processing
mov ctr, ParamC 'Set up a counter for number of bytes to process
call #ReadMulti 'Read the byte from the W5100
rINDcmd_ret ret 'Command execution complete
'-----------------------------------------------------------------------------------------------------
'Command sub-routine to write a register in the W5100 - a high level call
'-----------------------------------------------------------------------------------------------------
wINDcmd
mov reg, paramA 'Move the register address into a variable for processing
mov ram, paramB 'Move the data byte into a variable for processing
mov ctr, ParamC 'Set up a counter for number of bytes to process
call #writeMulti 'Write the byte to the W5100
wINDcmd_ret ret 'Command execution complete
'-----------------------------------------------------------------------------------------------------
'Command sub-routine to write the MAC ID in the W5100
'-----------------------------------------------------------------------------------------------------
wMAC
mov reg, #_SHAR0 'Move the MAC ID register address into a variable for processing
mov ram, cmdAdd 'Move the address of the MAC ID array into a variable for processing
mov ctr, #6 'Set up a counter of 6 bytes
call #WriteMulti 'Write the bytes out to the W5100
wMAC_ret ret 'Command execution complete
'-----------------------------------------------------------------------------------------------------
'Command sub-routine to write the Gateway address in the W5100
'-----------------------------------------------------------------------------------------------------
wGateway
mov reg, #_GAR0 'Move the gateway register address into a variable for processing
mov ram, cmdAdd 'Move the address of the gateway address array into a variable for processing
mov ctr, #4 'Set up a counter of 4 bytes
call #WriteMulti 'Write the bytes out to the W5100
wGateway_ret ret 'Command execution complete
'-----------------------------------------------------------------------------------------------------
'Command sub-routine to write the Subnet address in the W5100
'-----------------------------------------------------------------------------------------------------
wSubnet
mov reg, #_SUBR0 'Move the subnet register address into a variable for processing
mov ram, cmdAdd 'Move the address of the subnet address array into a variable for processing
mov ctr, #4 'Set up a counter of 4 bytes
call #WriteMulti 'Write the bytes out to the W5100
wSubnet_ret ret 'Command execution complete
'-----------------------------------------------------------------------------------------------------
'Command sub-routine to write the IP address in the W5100
'-----------------------------------------------------------------------------------------------------
wIP
mov reg, #_SIPR0 'Move the IP register address into a variable for processing
mov ram, cmdAdd 'Move the address of the IP address array into a variable for processing
mov ctr, #4 'Set up a counter of 4 bytes
call #WriteMulti 'Write the bytes out to the W5100
wIP_ret ret 'Command execution complete
'-----------------------------------------------------------------------------------------------------
'Command sub-routine to Read the MAC ID in the W5100
'-----------------------------------------------------------------------------------------------------
rMAC
mov reg, #_SHAR0 'Move the MAC ID register address into a variable for processing
mov ram, cmdAdd 'Move the address of the MAC ID array into a variable for processing
mov ctr, #6 'Set up a counter of 6 bytes
call #ReadMulti 'Read the bytes from the W5100
rMAC_ret ret 'Command execution complete
'-----------------------------------------------------------------------------------------------------
'Command sub-routine to Read the Gateway address in the W5100
'-----------------------------------------------------------------------------------------------------
rGateway
mov reg, #_GAR0 'Move the gateway register address into a variable for processing
mov ram, cmdAdd 'Move the address of the gateway address array into a variable for processing
mov ctr, #4 'Set up a counter of 4 bytes
call #ReadMulti 'Read the bytes from the W5100
rGateway_ret ret 'Command execution complete
'-----------------------------------------------------------------------------------------------------
'Command sub-routine to Read the Subnet address in the W5100
'-----------------------------------------------------------------------------------------------------
rSubnet
mov reg, #_SUBR0 'Move the subnet register address into a variable for processing
mov ram, cmdAdd 'Move the address of the subnet address array into a variable for processing
mov ctr, #4 'Set up a counter of 4 bytes
call #ReadMulti 'Read the bytes from the W5100
rSubnet_ret ret 'Command execution complete
'-----------------------------------------------------------------------------------------------------
'Command sub-routine to Read the IP address in the W5100
'-----------------------------------------------------------------------------------------------------
rIP
mov reg, #_SIPR0 'Move the IP register address into a variable for processing
mov ram, cmdAdd 'Move the address of the IP address array into a variable for processing
mov ctr, #4 'Set up a counter of 4 bytes
call #ReadMulti 'Read the bytes from the W5100
rIP_ret ret 'Command execution complete
'-----------------------------------------------------------------------------------------------------
'Command sub-routine for enabling/disabling a ping response by the W5100, true = blocked, false = not blocked
'-----------------------------------------------------------------------------------------------------
pingBlk
call #rIndirectMR 'Read the byte out of the mode register - so we don't overwrite current settings
rdlong t0, cmdAdd 'Read the bool from SPIN command and place in a variable for testing
cmp t0, #0 wz 'Is the value zero or non-zero?
if_z andn data, #_PBMode 'Disable ping blocking - W5100 will respond to a ping
if_nz or data, #_PBMode 'Enable ping blocking - W5100 will not respond to a ping
call #wIndirectMR 'Write the modified byte out to the mode register
pingBlk_ret ret 'Command execution complete
'-----------------------------------------------------------------------------------------------------
'Command sub-routine for resetting the W5100 via hardware
'-----------------------------------------------------------------------------------------------------
rstHW
andn outa, RESETmask 'Toggle the reset line low - resets the W5100
mov t0, _rstTime 'Time to hold IC in reset
add t0, cnt 'Add in the current system counter
waitcnt t0, _UnrstTime 'Wait
or outa, RESETmask 'Finally - make the reset line high for the W5100 to come out of reset
waitcnt t0, _UnrstTime 'Time to wait to come out of reset
rstHW_ret ret 'Command execution complete
'-----------------------------------------------------------------------------------------------------
'Command sub-routine for resetting the W5100 via software
'-----------------------------------------------------------------------------------------------------
rstSW
mov reg, #_MR 'Move the mode register address into a variable for processing
call #ReadSingle 'Read the byte from the W5100
or data, #_RSTMODE 'Software reset
call #writeSingle 'Write the byte to the W5100
mov t0, _UnrstTime 'Time to wait to come out of reset
add t0, cnt 'Add in the current system counter
waitcnt t0, #0 'Wait
rstSW_ret ret 'Command execution complete
'-----------------------------------------------------------------------------------------------------
'Command sub-routine for opening a socket
'this routine relies upond t0 being persistant across calls to writing routines
'-----------------------------------------------------------------------------------------------------
sOPEN
mov t0, paramA 'Move the socket number into t0 ($000s)
shl t0, #8 'Move the socket number to the third digit ($0s00). This is the offset to use for below ops.
'set the mode
mov reg, _S0_MR_d 'Move the register address into a variable for processing
add reg, t0 'Add in the offset for the particular socket to be worked on
mov data, paramB 'Move over the socket type
call #WriteSingle 'Write the byte to the W5100
'set the source port
mov reg, _S0_PORT1_d 'Move the register address into a variable for processing
add reg, t0 'Add in the offset for the particular socket to be worked on
mov data, paramC 'Move over the source socket value
call #WriteSingle 'Write the byte to the W5100
sub reg, #1 'Increment the register address
mov data, paramC 'Move over the source socket value
shr data, #8 'shift the data over one byte
call #WriteSingle 'Write the byte to the W5100
'set the destination port
mov reg, _S0_DPORT1_d 'Move the register address into a variable for processing
add reg, t0 'Add in the offset for the particular socket to be worked on
mov data, paramD 'Move over the destination socket value
call #WriteSingle 'Write the byte to the W5100
sub reg, #1 'Increment the register address
mov data, paramD 'Move over the destination socket value
shr data, #8 'shift the data over one byte
call #WriteSingle 'Write the byte to the W5100
'set the destination IP
mov reg, _S0_DIPR0_d 'Move the register address into a variable for processing
add reg, t0 'Add in the offset for the particular socket to be worked on
mov ram, paramE 'Move the address of the IP address array into a variable for processing
mov ctr, #4 'Set up a counter of 4 bytes
call #WriteMulti 'Write the bytes out to the W5100
'set the port open
mov reg, _S0_CR_d 'Move the register address into a variable for processing
add reg, t0 'Add in the offset for the particular socket to be worked on
mov data, #_OPEN 'Move over the command
call #WriteSingle 'Write the byte to the W5100
sOPEN_ret ret 'Command execution complete
'-----------------------------------------------------------------------------------------------------
'Command sub-routine for disconnecting a socket
'-----------------------------------------------------------------------------------------------------
sDISCON
mov t0, paramA 'Move the socket number into t0 ($000s)
shl t0, #8 'Move the socket number to the third digit ($0s00). This is the offset to use for below ops.
'set the port to disconnect
mov reg, _S0_CR_d 'Move the register address into a variable for processing
add reg, t0 'Add in the offset for the particular socket to be worked on
mov data, #_DISCON 'Move over the command
call #WriteSingle 'Write the byte to the W5100
sDISCON_ret ret 'Command execution complete
'-----------------------------------------------------------------------------------------------------
'Command sub-routine for closing a socket
'-----------------------------------------------------------------------------------------------------
sCLOSE
mov t0, paramA 'Move the socket number into t0 ($000s)
shl t0, #8 'Move the socket number to the third digit ($0s00). This is the offset to use for below ops.
'set the port close
mov reg, _S0_CR_d 'Move the register address into a variable for processing
add reg, t0 'Add in the offset for the particular socket to be worked on
mov data, #_CLOSE 'Move over the command
call #WriteSingle 'Write the byte to the W5100
sCLOSE_ret ret 'Command execution complete
'-----------------------------------------------------------------------------------------------------
'Command sub-routine holding place
'-----------------------------------------------------------------------------------------------------
LastCMD
LastCMD_ret ret 'Command execution complete
'-----------------------------------------------------------------------------------------------------
'-----------------------------------------------------------------------------------------------------
'Sub-routine to map write to SPI or Indirect/parallel
' data and reg setup before calling this routine
'-----------------------------------------------------------------------------------------------------
WriteSingle
call #wIndirectAR 'Set up the address register
call #wIndirectDRsetup 'Set up the data register for writing
call #wIndirectDRbyte 'Write the data
WriteSingle_ret ret 'Return to the calling code
'-----------------------------------------------------------------------------------------------------
'Sub-routine to map read to SPI or Indirect/parallel
' reg setup before calling this routine
'-----------------------------------------------------------------------------------------------------
ReadSingle
call #wIndirectAR 'Set up the address register
call #rIndirectDRsetup 'Set up the data registers for reading
call #rIndirectDRbyte 'Read the data
ReadSingle_ret ret 'Return to the calling code
'-----------------------------------------------------------------------------------------------------
'Sub-routine to map write to SPI or Indirect/parallel and to loop through bytes
' ram and reg and ctr setup before calling this routine
'-----------------------------------------------------------------------------------------------------
WriteMulti
call #wIndirectAR 'Set up the address register
call #wIndirectDRsetup 'Set up the data register for writing
:bytes rdbyte data, ram 'Read the byte/octet from hubram - this should also clear the DATA value, byte is zero extended
call #wIndirectDRbyte 'Write the data
add ram, #1 'Increment the hubram address by one byte
djnz ctr, #:bytes 'Check if there is another byte, if so, process it
WriteMulti_ret ret 'Return to the calling code
'-----------------------------------------------------------------------------------------------------
'Sub-routine to map read to SPI or Indirect/parallel and to loop through bytes
' reg and ctr setup before calling this routine
'-----------------------------------------------------------------------------------------------------
ReadMulti
call #wIndirectAR 'Set up the address register
call #rIndirectDRsetup 'Set up the data registers for reading
:bytes call #rIndirectDRbyte 'Read the data
wrbyte data, ram 'Write the byte to hubram
add ram, #1 'Increment the hubram address by one byte
djnz ctr, #:bytes 'Check if there is another if so, process it
ReadMulti_ret ret 'Return to the calling code
'-----------------------------------------------------------------------------------------------------
'Sub-routine to write to the indirect/parallel Mode Register
' data setup before calling this routine
'-----------------------------------------------------------------------------------------------------
wIndirectMR
or dira, DATAmask 'Ensure data pins are outputs
andn outa, ADD0mask 'Set Address to %00 - Mode Register access
andn outa, ADD1mask
and data, _bytemaskLSB 'Prepare the data, ensure there is only a byte in the data
shl data, BASEpin 'Move the data over so it is in the correct spot for copying to outa
andn outa, DATAmask 'Clear the data pins in outa
or outa, data 'Set the new data in outa
andn outa, WRCSmask 'Clear the WR and CS bits - W5100 reads the data
' nop 'Kill time - needed?
or outa, WRRDCSmask 'Turn on WR, RD, and CS bits - end of transaction
wIndirectMR_ret ret 'Return to the calling code
'-----------------------------------------------------------------------------------------------------
'Sub-routine to read from the indirect/parallel Mode Register
' data is returned in the lower byte
'-----------------------------------------------------------------------------------------------------
rIndirectMR
andn dira, DATAmask 'Ensure data pins are inputs
andn outa, ADD0mask 'Set Address to %00 - Mode Register access
andn outa, ADD1mask
andn outa, RDCSmask 'Clear the RD and CS bits
nop 'Kill time? Needed, maybe - see W5100 data sheet, may even want 2 nops
mov data, ina 'Copy data from ina
or outa, WRRDCSmask 'Turn on WR, RD, and CS bits
shr data, BASEpin 'Move the byte to the lowest byte
and data, _bytemaskLSB 'Finalize the data to have only the lowest byte
rIndirectMR_ret ret 'Return to the calling code
'-----------------------------------------------------------------------------------------------------
'Sub-routine to write to the indirect/parallel Address Registers
' reg setup before calling this routine
'-----------------------------------------------------------------------------------------------------
wIndirectAR
'MSB address byte
or dira, DATAmask 'Ensure data pins are outputs
or outa, ADD0mask 'Set Address to %01 - MSB address Register access
andn outa, ADD1mask
mov t1, reg 'Get a copy of reg for manipulating
and t1, _bytemaskMSB 'Prepare the byte for sending to W5100
shr t1, #8 'Right justify the byte
shl t1, BASEpin 'Move the byte to the data lines
andn outa, DATAmask 'Clear the data pins in outa
or outa, t1 'Set the MSB address in outa
andn outa, WRCSmask 'Clear the WR and CS bits - W5100 reads the data
' nop 'Kill time - needed?
or outa, WRRDCSmask 'Turn on WR, RD, and CS bits - end of transaction
'LSB address byte
andn outa, ADD0mask 'Set Address to %10 - LSB address Register access
or outa, ADD1mask
mov t1, reg 'Get a copy of reg for manipulating
and t1, _bytemaskLSB 'Prepare the byte for sending to W5100
shl t1, BASEpin 'Move the byte to the data lines
andn outa, DATAmask 'Clear the data pins in outa
or outa, t1 'Set the LSB address in outa
andn outa, WRCSmask 'Clear the WR and CS bits - W5100 reads the data
' nop 'Kill time - needed?
or outa, WRRDCSmask 'Turn on WR, RD, and CS bits - end of transaction
wIndirectAR_ret ret 'Return to the calling code
'-----------------------------------------------------------------------------------------------------
'Sub-routine to setup for writing to the indirect/parallel Data Registers
'-----------------------------------------------------------------------------------------------------
wIndirectDRsetup
or dira, DATAmask 'Ensure data pins are outputs
or outa, ADD0mask 'Set Address to %11 - Data Register access
or outa, ADD1mask
wIndirectDRsetup_ret ret 'Return to the calling code
'-----------------------------------------------------------------------------------------------------
'Sub-routine to write to the indirect/parallel Data Registers - must be setup first
' data setup before calling this routine
'-----------------------------------------------------------------------------------------------------
wIndirectDRbyte
shl data, BASEpin 'Move the data over so it is in the correct spot for copying to outa
andn outa, DATAmask 'Clear the data pins in outa
or outa, data 'Set the new data in outa
andn outa, WRCSmask 'Clear the WR and CS bits - W5100 reads the data
' nop 'Kill time - needed?
or outa, WRRDCSmask 'Turn on WR, RD, and CS bits - end of transaction
wIndirectDRbyte_ret ret 'Return to the calling code
'-----------------------------------------------------------------------------------------------------
'Sub-routine to setup for reading from the indirect/parallel Data Registers
'-----------------------------------------------------------------------------------------------------
rIndirectDRsetup
andn dira, DATAmask 'Ensure data pins are inputs
or outa, ADD0mask 'Set Address to %11 - Data Register access
or outa, ADD1mask
rIndirectDRsetup_ret ret 'Return to the calling code
'-----------------------------------------------------------------------------------------------------
'Sub-routine to read from the indirect/parallel Data Registers - must be setup first
' data is returned in the lower byte
'-----------------------------------------------------------------------------------------------------
rIndirectDRbyte
andn outa, RDCSmask 'Clear the RD and CS bits
nop 'Kill time? Needed, maybe - see W5100 data sheet, may even want 2 nops
mov data, ina 'Copy data from ina
or outa, WRRDCSmask 'Turn on WR, RD, and CS bits
shr data, BASEpin 'Move the byte to the lowest byte
and data, _bytemaskLSB 'Finalize the data to have only the lowest byte
rIndirectDRbyte_ret ret 'Return to the calling code
DAT
'-----------------------------------------------------------------------------------------------------
'-----------------------------------------------------------------------------------------------------
'Defined data
_zero long 0 'Zero
_bytemaskLSB long $FF 'Byte mask LSB
_bytemaskMSB long $FF00 'Byte mask MSB
_wordmask long $FFFF 'Word mask
_rstTime long 100_000 'Time to hold in reset
_UnrstTime long 200_000 'Time to wait coming out of reset
'Pin/mask definitions are initianlized in SPIN and program/memory modified here before the COG is started
ADD0mask long 0-0 'W5100 Address[0] - output
ADD1mask long 0-0 'W5100 Address[1] - output
CSmask long 0-0 'W5100 Chip Select - active low, output
RDmask long 0-0 'W5100 Read cmd - active low, output
WRmask long 0-0 'W5100 Write cmd - active low, output
RESETmask long 0-0 'W5100 Reset - active low, output
BASEpin long 0-0 'Base pin of data byte for shifting operation
DATAmask long 0-0 'W5100 Data[0] to [7] - output/input
WRCSmask long 0-0 '
RDCSmask long 0-0 '
WRRDCSmask long 0-0 '
SENmask long 0-0 'W5100 SPI Enable 0 output, low = indirect/parallel, high = SPI
'Data defined in constant section, but needed in the ASM for program operation
_S0_MR_d long _S0_MR
_S0_PORT1_d long _S0_PORT1
_S0_DPORT1_d long _S0_DPORT1
_S0_DIPR0_d long _S0_DIPR0
_S0_CR_d long _S0_CR
'-----------------------------------------------------------------------------------------------------
'-----------------------------------------------------------------------------------------------------
'Uninitialized data
'temporary variables
t0 res 1 'temp0
t1 res 1 'temp1
'Parameters read from commands passed into the ASM routine
cmdAdd res 1 'Combo of command and address passed into ASM
paramA res 1 'Parameter A
paramB res 1 'Parameter B
paramC res 1 'Parameter C
paramD res 1 'Parameter D
paramE res 1 'Parameter E
reg res 1 'Register address of W5100 for processing
ram res 1 'Ram address of Prop Hubram for reading/writing data from
ctr res 1 'Counter of bytes for looping
data res 1 'Data read to/from the W5100
fit 496 'Ensure the ASM program and defined/res variables fit in a COG.
{{
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ 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. │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
}}
{{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}}
{{
─────────────────────────────────────────────────
File: Parallax Serial Terminal.spin
Version: 1.0
Copyright (c) 2009 Parallax, Inc.
See end of file for terms of use.
Authors: Jeff Martin, Andy Lindsay, Chip Gracey
─────────────────────────────────────────────────
}}
{
HISTORY:
This object is made for direct use with the Parallax Serial Terminal; a simple serial communication program
available with the Propeller Tool installer and also separately via the Parallax website (www.parallax.com).
This object is heavily based on FullDuplexSerialPlus (by Andy Lindsay), which is itself heavily based on
FullDuplexSerial (by Chip Gracey).
USAGE:
• Call Start, or StartRxTx, first.
• Be sure to set the Parallax Serial Terminal software to the baudrate specified in Start, and the proper COM port.
• At 80 MHz, this object properly receives/transmits at up to 250 Kbaud, or performs transmit-only at up to 1 Mbaud.
}
CON
''
'' Parallax Serial Terminal
'' Control Character Constants
''─────────────────────────────────────
CS = 16 ''CS: Clear Screen
CE = 11 ''CE: Clear to End of line
CB = 12 ''CB: Clear lines Below
HM = 1 ''HM: HoMe cursor
PC = 2 ''PC: Position Cursor in x,y
PX = 14 ''PX: Position cursor in X
PY = 15 ''PY: Position cursor in Y
NL = 13 ''NL: New Line
LF = 10 ''LF: Line Feed
ML = 3 ''ML: Move cursor Left
MR = 4 ''MR: Move cursor Right
MU = 5 ''MU: Move cursor Up
MD = 6 ''MD: Move cursor Down
TB = 9 ''TB: TaB
BS = 8 ''BS: BackSpace
BP = 7 ''BP: BeeP speaker
CON
BUFFER_LENGTH = 64 'Recommended as 64 or higher, but can be 2, 4, 8, 16, 32, 64, 128 or 256.
BUFFER_MASK = BUFFER_LENGTH - 1
MAXSTR_LENGTH = 49 'Maximum length of received numerical string (not including zero terminator).
VAR
long cog 'Cog flag/id
long rx_head '9 contiguous longs (must keep order)
long rx_tail
long tx_head
long tx_tail
long rx_pin
long tx_pin
long rxtx_mode
long bit_ticks
long buffer_ptr
byte rx_buffer[BUFFER_LENGTH] 'Receive and transmit buffers
byte tx_buffer[BUFFER_LENGTH]
byte str_buffer[MAXSTR_LENGTH+1] 'String buffer for numerical strings
PUB Start(baudrate) : okay
{{Start communication with the Parallax Serial Terminal using the Propeller's programming connection.
Waits 1 second for connection, then clears screen.
Parameters:
baudrate - bits per second. Make sure it matches the Parallax Serial Terminal's
Baud Rate field.
Returns : True (non-zero) if cog started, or False (0) if no cog is available.}}
okay := StartRxTx(31, 30, 0, baudrate)
waitcnt(clkfreq + cnt) 'Wait 1 second for PST
Clear 'Clear display
PUB StartRxTx(rxpin, txpin, mode, baudrate) : okay
{{Start serial communication with designated pins, mode, and baud.
Parameters:
rxpin - input pin; receives signals from external device's TX pin.
txpin - output pin; sends signals to external device's RX pin.
mode - signaling mode (4-bit pattern).
bit 0 - inverts rx.
bit 1 - inverts tx.
bit 2 - open drain/source tx.
bit 3 - ignore tx echo on rx.
baudrate - bits per second.
Returns : True (non-zero) if cog started, or False (0) if no cog is available.}}
stop
longfill(@rx_head, 0, 4)
longmove(@rx_pin, @rxpin, 3)
bit_ticks := clkfreq / baudrate
buffer_ptr := @rx_buffer
okay := cog := cognew(@entry, @rx_head) + 1
PUB Stop
{{Stop serial communication; frees a cog.}}
if cog
cogstop(cog~ - 1)
longfill(@rx_head, 0, 9)
PUB Char(bytechr)
{{Send single-byte character. Waits for room in transmit buffer if necessary.
Parameter:
bytechr - character (ASCII byte value) to send.}}
repeat until (tx_tail <> ((tx_head + 1) & BUFFER_MASK))
tx_buffer[tx_head] := bytechr
tx_head := (tx_head + 1) & BUFFER_MASK
if rxtx_mode & %1000
CharIn
PUB Chars(bytechr, count)
{{Send multiple copies of a single-byte character. Waits for room in transmit buffer if necessary.
Parameters:
bytechr - character (ASCII byte value) to send.
count - number of bytechrs to send.}}
repeat count
Char(bytechr)
PUB CharIn : bytechr
{{Receive single-byte character. Waits until character received.
Returns: $00..$FF}}
repeat while (bytechr := RxCheck) < 0
PUB Str(stringptr)
{{Send zero terminated string.
Parameter:
stringptr - pointer to zero terminated string to send.}}
repeat strsize(stringptr)
Char(byte[stringptr++])
PUB StrIn(stringptr)
{{Receive a string (carriage return terminated) and stores it (zero terminated) starting at stringptr.
Waits until full string received.
Parameter:
stringptr - pointer to memory in which to store received string characters.
Memory reserved must be large enough for all string characters plus a zero terminator.}}
StrInMax(stringptr, -1)
PUB StrInMax(stringptr, maxcount)
{{Receive a string of characters (either carriage return terminated or maxcount in length) and stores it (zero terminated)
starting at stringptr. Waits until either full string received or maxcount characters received.
Parameters:
stringptr - pointer to memory in which to store received string characters.
Memory reserved must be large enough for all string characters plus a zero terminator (maxcount + 1).
maxcount - maximum length of string to receive, or -1 for unlimited.}}
repeat while (maxcount--) 'While maxcount not reached
if (byte[stringptr++] := CharIn) == NL 'Get chars until NL
quit
byte[stringptr+(byte[stringptr-1] == NL)]~ 'Zero terminate string; overwrite NL or append 0 char
PUB Dec(value) | i, x
{{Send value as decimal characters.
Parameter:
value - byte, word, or long value to send as decimal characters.}}
x := value == NEGX 'Check for max negative
if value < 0
value := ||(value+x) 'If negative, make positive; adjust for max negative
Char("-") 'and output sign
i := 1_000_000_000 'Initialize divisor
repeat 10 'Loop for 10 digits
if value => i
Char(value / i + "0" + x*(i == 1)) 'If non-zero digit, output digit; adjust for max negative
value //= i 'and digit from value
result~~ 'flag non-zero found
elseif result or i == 1
Char("0") 'If zero digit (or only digit) output it
i /= 10 'Update divisor
PUB DecIn : value
{{Receive carriage return terminated string of characters representing a decimal value.
Returns: the corresponding decimal value.}}
StrInMax(@str_buffer, MAXSTR_LENGTH)
value := StrToBase(@str_buffer, 10)
PUB Bin(value, digits)
{{Send value as binary characters up to digits in length.
Parameters:
value - byte, word, or long value to send as binary characters.
digits - number of binary digits to send. Will be zero padded if necessary.}}
value <<= 32 - digits
repeat digits
Char((value <-= 1) & 1 + "0")
PUB BinIn : value
{{Receive carriage return terminated string of characters representing a binary value.
Returns: the corresponding binary value.}}
StrInMax(@str_buffer, MAXSTR_LENGTH)
value := StrToBase(@str_buffer, 2)
PUB Hex(value, digits)
{{Send value as hexadecimal characters up to digits in length.
Parameters:
value - byte, word, or long value to send as hexadecimal characters.
digits - number of hexadecimal digits to send. Will be zero padded if necessary.}}
value <<= (8 - digits) << 2
repeat digits
Char(lookupz((value <-= 4) & $F : "0".."9", "A".."F"))
PUB HexIn : value
{{Receive carriage return terminated string of characters representing a hexadecimal value.
Returns: the corresponding hexadecimal value.}}
StrInMax(@str_buffer, MAXSTR_LENGTH)
value := StrToBase(@str_buffer, 16)
PUB Clear
{{Clear screen and place cursor at top-left.}}
Char(CS)
PUB ClearEnd
{{Clear line from cursor to end of line.}}
Char(CE)
PUB ClearBelow
{{Clear all lines below cursor.}}
Char(CB)
PUB Home
{{Send cursor to home position (top-left).}}
Char(HM)
PUB Position(x, y)
{{Position cursor at column x, row y (from top-left).}}
Char(PC)
Char(x)
Char(y)
PUB PositionX(x)
{{Position cursor at column x of current row.}}
Char(PX)
Char(x)
PUB PositionY(y)
{{Position cursor at row y of current column.}}
Char(PY)
Char(y)
PUB NewLine
{{Send cursor to new line (carriage return plus line feed).}}
Char(NL)
PUB LineFeed
{{Send cursor down to next line.}}
Char(LF)
PUB MoveLeft(x)
{{Move cursor left x characters.}}
repeat x
Char(ML)
PUB MoveRight(x)
{{Move cursor right x characters.}}
repeat x
Char(MR)
PUB MoveUp(y)
{{Move cursor up y lines.}}
repeat y
Char(MU)
PUB MoveDown(y)
{{Move cursor down y lines.}}
repeat y
Char(MD)
PUB Tab
{{Send cursor to next tab position.}}
Char(TB)
PUB Backspace
{{Delete one character to left of cursor and move cursor there.}}
Char(BS)
PUB Beep
{{Play bell tone on PC speaker.}}
Char(BP)
PUB RxCount : count
{{Get count of characters in receive buffer.
Returns: number of characters waiting in receive buffer.}}
count := rx_head - rx_tail
count -= BUFFER_LENGTH*(count < 0)
PUB RxFlush
{{Flush receive buffer.}}
repeat while rxcheck => 0
PRI RxCheck : bytechr
{Check if character received; return immediately.
Returns: -1 if no byte received, $00..$FF if character received.}
bytechr~~
if rx_tail <> rx_head
bytechr := rx_buffer[rx_tail]
rx_tail := (rx_tail + 1) & BUFFER_MASK
PRI StrToBase(stringptr, base) : value | chr, index
{Converts a zero terminated string representation of a number to a value in the designated base.
Ignores all non-digit characters (except negative (-) when base is decimal (10)).}
value := index := 0
repeat until ((chr := byte[stringptr][index++]) == 0)
chr := -15 + --chr & %11011111 + 39*(chr > 56) 'Make "0"-"9","A"-"F","a"-"f" be 0 - 15, others out of range
if (chr > -1) and (chr < base) 'Accumulate valid values into result; ignore others
value := value * base + chr
if (base == 10) and (byte[stringptr] == "-") 'If decimal, address negative sign; ignore otherwise
value := - value
DAT
'***********************************
'* Assembly language serial driver *
'***********************************
org
'
'
' Entry
'
entry mov t1,par 'get structure address
add t1,#4 << 2 'skip past heads and tails
rdlong t2,t1 'get rx_pin
mov rxmask,#1
shl rxmask,t2
add t1,#4 'get tx_pin
rdlong t2,t1
mov txmask,#1
shl txmask,t2
add t1,#4 'get rxtx_mode
rdlong rxtxmode,t1
add t1,#4 'get bit_ticks
rdlong bitticks,t1
add t1,#4 'get buffer_ptr
rdlong rxbuff,t1
mov txbuff,rxbuff
add txbuff,#BUFFER_LENGTH
test rxtxmode,#%100 wz 'init tx pin according to mode
test rxtxmode,#%010 wc
if_z_ne_c or outa,txmask
if_z or dira,txmask
mov txcode,#transmit 'initialize ping-pong multitasking
'
'
' Receive
'
receive jmpret rxcode,txcode 'run chunk of tx code, then return
test rxtxmode,#%001 wz 'wait for start bit on rx pin
test rxmask,ina wc
if_z_eq_c jmp #receive
mov rxbits,#9 'ready to receive byte
mov rxcnt,bitticks
shr rxcnt,#1
add rxcnt,cnt
:bit add rxcnt,bitticks 'ready next bit period
:wait jmpret rxcode,txcode 'run chunk of tx code, then return
mov t1,rxcnt 'check if bit receive period done
sub t1,cnt
cmps t1,#0 wc
if_nc jmp #:wait
test rxmask,ina wc 'receive bit on rx pin
rcr rxdata,#1
djnz rxbits,#:bit
shr rxdata,#32-9 'justify and trim received byte
and rxdata,#$FF
test rxtxmode,#%001 wz 'if rx inverted, invert byte
if_nz xor rxdata,#$FF
rdlong t2,par 'save received byte and inc head
add t2,rxbuff
wrbyte rxdata,t2
sub t2,rxbuff
add t2,#1
and t2,#BUFFER_MASK
wrlong t2,par
jmp #receive 'byte done, receive next byte
'
'
' Transmit
'
transmit jmpret txcode,rxcode 'run chunk of rx code, then return
mov t1,par 'check for head <> tail
add t1,#2 << 2
rdlong t2,t1
add t1,#1 << 2
rdlong t3,t1
cmp t2,t3 wz
if_z jmp #transmit
add t3,txbuff 'get byte and inc tail
rdbyte txdata,t3
sub t3,txbuff
add t3,#1
and t3,#BUFFER_MASK
wrlong t3,t1
or txdata,#$100 'ready byte to transmit
shl txdata,#2
or txdata,#1
mov txbits,#11
mov txcnt,cnt
:bit test rxtxmode,#%100 wz 'output bit on tx pin
test rxtxmode,#%010 wc 'according to mode
if_z_and_c xor txdata,#1
shr txdata,#1 wc
if_z muxc outa,txmask
if_nz muxnc dira,txmask
add txcnt,bitticks 'ready next cnt
:wait jmpret txcode,rxcode 'run chunk of rx code, then return
mov t1,txcnt 'check if bit transmit period done
sub t1,cnt
cmps t1,#0 wc
if_nc jmp #:wait
djnz txbits,#:bit 'another bit to transmit?
jmp #transmit 'byte done, transmit next byte
'
'
' Uninitialized data
'
t1 res 1
t2 res 1
t3 res 1
rxtxmode res 1
bitticks res 1
rxmask res 1
rxbuff res 1
rxdata res 1
rxbits res 1
rxcnt res 1
rxcode res 1
txmask res 1
txbuff res 1
txdata res 1
txbits res 1
txcnt res 1
txcode res 1
{{
┌──────────────────────────────────────────────────────────────────────────────────────┐
│ 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. │
└──────────────────────────────────────────────────────────────────────────────────────┘
}}
{{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}}
CON
_clkmode = xtal1 + pll16x 'Use the PLL to multiple the external clock by 16
_xinfreq = 5_000_000 'An external clock of 5MHz. is used (80MHz. operation)
CON
CR = 13
LF = 10
NBSP = 32
HTTP_ERROR = 0
HTTP_GET = 1
HTTP_POST = 2
HTTP_10 = 0
HTTP_11 = 1
DYNAMIC = 3
FS = "/"
EQ = "="
AMP = "&"
QM = "?"
DOT = "."
STR_TERM = 0
OBJ
DAT
get byte "GET", 0
post byte "POST", 0
http byte "HTTP/1.1", 0
host byte "Host:", 0
contlen byte "Content-Length:", 0
wbody byte CR, LF, CR, LF, 0
lbody byte LF, LF, 0
dynamicpage byte "SHP", 0
defaultpage byte "index.htm", 0, "default.htm", 0
null byte 0
filestack byte $0[20], 0
hostname byte $0[20], 0
asciiNum byte $0[10], 0
dirstack byte $0[256], 0
url byte $0[512], 0
varstack byte $0[512], 0
VAR
byte method
byte httpVersion
byte fileType
byte directoryStackDepth
byte postback
long ptrStart
long ptrEnd
long contentLength
long header
long directoryStackPtr[10]
long fileStackPtr[2]
long fileErrorHandle
long varstackPtr
'PUB main
PUB Initialize(data)
header := data
bytefill(@dirstack, 0, 256)
bytefill(@filestack, 0, 20)
bytefill(@varstack, 0, 512)
bytefill(@url, 0, 512)
directoryStackPtr[0] := @dirstack
varstackPtr := @varstack
FillHTTPMethod
FillHeaderValue(@host, @hostname)
FillContentLength
FillURL
EnumerateUrl
FillFileType
'Check the if post and the body exists
'Had a problem with Opera
'if(method == HTTP_POST)
PUB GetDirectoryStackDepth | i
i := PeekDir(directoryStackDepth - 1)
if(strsize(i) > 0)
result := directoryStackDepth - 1
else
result := -1
PUB Filename | i
' Get the file name
i := PeekDir(directoryStackDepth-1)
if(strsize(i) > 0)
result := i
else
result := @defaultpage
PUB Directory(index)
result := PeekDir(index)
PUB GetFileType
result := fileType
PUB FilenameNoExtension
result := @filestack
PUB FileExtension
result := filestackptr[1]
PUB GetUrlPtr
result := @url
PUB GetMethod
result := method
PUB GetContentLength
return contentLength
PUB GetAsciiNum
result := @asciiNum
PUB FillHTTPMethod
if(MatchPattern(header, @get, false, 0) > -1)
method := HTTP_GET
return
if(MatchPattern(header, @post, false, 0) > -1)
method := HTTP_POST
return
method := HTTP_ERROR
PUB BodyLength | s, l, offset
' check the content length
if(contentLength == 0)
return
' Position the pointer to the body section of the header
offset := MatchPattern(header, @wbody, true, 0)
if(offset <> -1)
' Move the offset past the search string
offset := offset + strsize(@wbody)
else
offset := MatchPattern(header, @lbody, true, 0)
if(offset == -1)
return
else
' Move the offset past the search string
offset := offset + strsize(@lbody)
return strsize(header) - offset
PUB __POST(name) | s, e, l, offset, t1
result := @null
' Make sure that we have a post
if(method <> HTTP_POST)
return
' check the content length
if(contentLength == 0)
return
' Position the pointer to the body section of the header
offset := MatchPattern(header, @wbody, true, 0)
if(offset <> -1)
' Move the offset past the search string
offset := offset + strsize(@wbody)
else
offset := MatchPattern(header, @lbody, true, 0)
if(offset == -1)
return
else
' Move the offset past the search string
offset := offset + strsize(@lbody)
' Find the name/value
offset := MatchPattern(header, name, false, offset)
if(offset == -1)
return
offset := offset + strsize(name)
s := InString(header, EQ, false, offset)
e := InString(header, AMP, false, s)
if(e == -1)
e := InString(header, STR_TERM, false, s)
if((s == e-1) or (s == e))
return
s := s + 1
l := e - s
s := s + header
' push variable on the varstack
' and return a pointer
return PushVarStack(s, l)
PUB __GET(name) | s, e, offset, l
'Parse the query string
result := @null
' go to the start fo the querystring
s := InString(@url, QM, true, 0)
if(s == -1)
return
offset := MatchPattern(@url, name, true, s)
if(offset == -1)
return
offset := offset + strsize(name)
s := InString(@url, EQ, true, offset)
e := InString(@url, AMP, true, s)
if(e == -1)
e := InString(@url, STR_TERM, true, s)
' If the character after the "=" is a "&"
' then we have an empty value
if((s == e-1) or (s == e))
return
s := s + 1
l := e - s
s := s + @url
return PushVarStack(s, l)
PRI PushVarStack(startAddress, bytesToWrite) | e, temp
' save the varstackPtr address
temp := varstackPtr
' Write the variable to memory
bytemove(varstackPtr, startAddress, bytesToWrite)
' Terminate the string with the old 0
e := varstackPtr + bytesToWrite
bytefill(e, STR_TERM, 1)
' Decode the string
' This might make the string a little smaller
DecodeString(varstackPtr)
'Update the pointer so it's pointing to the next address
varstackPtr := strsize(varstackPtr) + 1
' Return a pointer to the string we just pushed on the stack
return temp
PRI DecodeString(decodeStr) | char, inPlace, outPlace
inPlace := outPlace := 0
repeat
char := byte[decodeStr][inPlace++]
if (char == "%") ' convert %## back into a character
'inPlace++ ' skip %
' first nibble
char := byte[decodeStr][inPlace++] - 48
if (char > 9)
char -= 7
char := char << 4
byte[decodeStr][outPlace] := char
' second nibble
char := byte[decodeStr][inPlace++] - 48
if (char > 9)
char -= 7
byte[decodeStr][outPlace++] += char
' since we trashed char doing the decode, we need this to keep the loop going
char := "x"
elseif (char == "+") ' convert + back to a space
byte[decodeStr][outPlace++] := " "
else ' no conversion needed, just set the character
byte[decodeStr][outPlace++] := char
until (char == 0)
byte[decodeStr][outPlace-1] := 0 ' terminate the string at it's new shorter size
PUB GetFilePath | s, e
ptrStart := 0
ptrEnd := 0
s := InString(@url, FS, true, 0)
' go to the start fo the querystring
e := InString(@url, QM, true, 0)
if( e == -1)
e := InString(@url, STR_TERM, true, 0)
ptrStart := s
ptrEnd := e-1
if(s == e)
ptrStart := 0
ptrEnd := 1
PUB FillFileType | s, e, i, temp, len
fileType := 0
temp := Filename
'fileStackPtr[0] := @filestack
' Get filename length
len := STRSIZE(temp)
' Get the dot position
' We're expecting 8.3
i := InString(temp, DOT, true, 0)
'Fill the filestack
bytemove(@filestack, temp, len)
' Replace the "." with a string terminator
' We're expecting 1 dot
bytefill(@filestack+i, STR_TERM, 1)
' Get the position of the extension
fileStackPtr[1] := @filestack+i+1
'TODO - remove this as we always load a default file
if(i == -1)
fileType := 1
ptrStart := 0
ptrEnd := 1
return
if(MatchPattern(@url, @dynamicpage, false, 0) == -1)
fileType := 2
else
fileType := 3
return
PUB IsMethod(name)
result := false
if(MatchPattern(@filestack, name, false, 0) > -1)
result := true
PUB EnumerateUrl | i, e, s, t
directoryStackDepth := 0
' Find the end of the url, "?" or "0"
e := InString(@url, QM, true, 0)
if( e == -1)
e := InString(@url, STR_TERM, true, 0)
' Prime the pointers
i := 0
s := 1
' Get the first "/" position
' Should be position 0
i := InString(@url, FS, true, 0)
repeat
i := InString(@url, FS, true, s)
if(i > -1)
'PushDir directory name on to the stack
PushDir(@url+s, i-s)
'Increment the start pointer so we're pointing
'to the next directory name character not the "/"
s := i+1
t := i
if(i == -1)
'PushDir the filename on the stack
PushDir(@url+s, e-s)
quit
PUB PushDir(startAddress, numberOfBytes) | e
' Pointer to the end of the string
e := directoryStackPtr[directoryStackDepth] + numberOfBytes
bytemove(directoryStackPtr[directoryStackDepth], startAddress, numberOfBytes)
bytefill(e, STR_TERM, 1)
directoryStackPtr[++directoryStackDepth] := e+1
PUB PeekDir(index)
if(index > directoryStackDepth-1)
result := -1
return
result := directoryStackPtr[index]
PUB FillURL | i, j, x, char
i := 0
j := 0
i := InString(header, FS, true, 0)
j := InString(header, NBSP, true, i+1)
'j := MatchPattern(header, string("HTTP"), false, i+1)
ptrStart := i
ptrEnd := j-1
bytemove(@url, header+i, j-i )
PUB IsHTTPVersion11
if(MatchPattern(header, @http, false, 0) > -1)
result := true
else
result := false
Pub FillContentLength | i, temp, s, e, l, offset, t1
offset := MatchPattern(header, @contlen, false, 0)
if(offset == -1)
contentLength := 0
return 0
offset := offset + strsize(@contlen)
s := offset + 1
e := InString(header, CR, false, offset)
l := e - offset - 1
s := s + header
if(e == -1)
contentLength := 0
return
t1 := l-1
contentLength := 0
repeat i from 0 to l-1
temp := PowerOfTen(t1--)
temp := (byte[s + i] - 48) * temp
contentLength := contentLength + temp
PRI PowerOfTen(numOfTens) | i, temp
temp := 1
if(numOfTens > 0)
repeat i from 1 to numOfTens
temp := 10 * temp
result := temp
PUB NumberToAsccii(num) | count, temp, t2, i, ptr
count := 0
temp := 1
bytefill(@asciiNum, 0, 10)
ptr := @asciiNum
if(num == 0)
byte[ptr++] := $30
repeat while i > 0
temp *= 10
count ++
i := num / temp
t2 := num
i := 0
repeat while count > 0
temp /= 10
byte[ptr++] := (t2 / temp) + $30
t2 -= temp * (t2 / temp)
count--
return @asciiNum
PUB FillHeaderValue(valueToFind, fillAddress) | s, e, l, offset
offset := MatchPattern(header, valueToFind, false, 0)
offset := offset + strsize(valueToFind)
e := InString(header, CR, false, offset)
s := offset + 1
l := e - offset - 1
s := s + header
bytemove(fillAddress, s, l)
bytefill(fillAddress + l, 0, 1)
PUB InString(source, char, matchCase, offset) | i, j, sourceLength, patternlength
sourceLength := strsize(source)
result := -1
repeat i from offset to sourceLength
if(IsCharMatch(source + i, @char, matchCase) )
result := i
quit
PUB MatchPattern(source, pattern, matchCase, startIndex) | i, j, sourceLength, patternlength
sourceLength := strsize(source)
patternlength := strsize(pattern)
result := -1
ptrEnd := 0
ptrStart := 0
i := 0
j := 0
repeat i from startIndex to sourceLength
' Try to match the first char in pattern to the
' source
if(IsCharMatch(source + i, pattern + j, matchCase) )
'Found a match
'Store the current pointer position and increment the source pointer by one byte
ptrStart := source + i
'Lets see if the pattern lines up with the source
repeat j from 0 to patternLength - 1
ifnot(IsCharMatch(source + i + j, pattern + j, matchCase))
j := 0
ptrEnd := -1
ptrStart := -1
quit
if(j > 0)
ptrEnd := ptrStart + patternLength
result := i
quit
PRI IsCharMatch(sourceChar, patternChar, matchCase)
'' Initilize the return value
result := false
if(matchCase)
if(byte[sourceChar] == byte[patternChar])
result := true
else
if((byte[sourceChar] == byte[patternChar]) or (byte[sourceChar] == byte[patternChar]+32))
result := true
'***************************************
PRI PauseMSec(Duration)
'***************************************
'' Pause execution for specified milliseconds.
'' This routine is based on the set clock frequency.
''
'' params: Duration = number of milliseconds to delay
'' return: none
waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)
return 'end of PauseMSec
DAT
Workspace BYTE 0
{{
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ 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. │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
}}
{{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}}
{{
SD2.0 FATEngine Wrapper
by Roy Eltham
11/18/2010
Copyright (c) 2010 Roy Eltham
See end of file for terms of use.
}}
CON
_clkfreq = 80_000_000
_clkmode = xtal1 + pll16x
_cardDataOutPin = 16
_cardClockPin = 21
_cardDataInPin = 20
_cardChipSelectPin = 19
OBJ
fat: "SD2.0_FATEngine.spin"
PUB Start
fat.FATEngineStart(_cardDataOutPin, _cardClockPin, _cardDataInPin, _cardChipSelectPin, 0, 0)
PUB checkError
return fat.checkErrorNumber
PUB mount(stringPointer)
return fat.mountPartition(0, stringPointer)
PUB unmount(stringPointer)
return fat.unmountPartition
PUB changeDirectory(directoryName)
return fat.changeDirectory(directoryName)
PUB getWorkingDirectory
return fat.listWorkingDirectory
PUB startFindFile
fat.listName("T")
PUB nextFindFile | temp, index
temp := fat.listName(" ")
repeat index from 0 to 11
if byte[temp][index] == 32
byte[temp][index] := 0
quit
return temp
PUB openFile(fileName, action)
return fat.openFile(fileName, action)
PUB closeFile
return fat.closeFile
PUB getFileSize
return fat.listSize
PUB readFromFile(bufferPtr, bufferSize)
return fat.readData(bufferPtr, bufferSize)
PUB flushData
fat.flushData
PUB deleteEntry(name)
return fat.deleteEntry(name)
PUB newFile(fileName)
return fat.newFile(fileName)
PUB writeData(addressToGet, count)
return fat.writeData(addressToGet, count)
PUB checkFilePosition '' 3 Stack Longs
return fat.checkFilePosition
PUB changeFilePosition(position)
return changeFilePosition(position)
PUB writeByte(value)
return fat.writeByte(value)
{{
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ 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. │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
}}
{{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}}
{{
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ SD2.0 File Allocation Table Engine │
│ │
│ Author: Kwabena W. Agyeman │
│ Updated: 5/27/2010 │
│ Designed For: P8X32A @ 80Mhz │
│ Version: 1.3 │
│ │
│ Copyright (c) 2010 Kwabena W. Agyeman │
│ See end of file for terms of use. │
│ │
│ Update History: │
│ │
│ v1.0 - Original release - 1/7/2010. │
│ v1.1 - Updated everything, made code abort less, caught more errors. - 3/10/2010. │
│ v1.2 - Added support for variable pin assignments and locks. - 3/12/2010. │
│ v1.3 - Improved code and added new features. - 5/27/2010. │
│ │
│ For each included copy of this object only one spin interpreter should access it at a time. │
│ │
│ Nyamekye, │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ SPI Circuit: │
│ │
│ Data Out Pin Number ─ Data out pin. Driven by the SD card. Goes to the SD card data out pin. │
│ │
│ Clock Pin Number ─ Clock pin. Driven by the propeller chip. Goes to the SD clock pin. │
│ │
│ Data In Pin Number ─ Data in pin. Driven by the propeller chip. Goes to the SD card data in pin. │
│ │
│ Chip Select Pin Number ─ Chip select pin. Driven by the propeller chip. Goes to the SD card chip select pin. │
│ │
│ 3.3V │
│ │
│ │ │
│ 10KΩ │
│ │ │
│ Real Time Clock Data Pin Number ─┻─ Goes to the DS1307 SDA Pin. │
│ │
│ 3.3V │
│ │
│ │ │
│ 10KΩ │
│ │ │
│ Real Time Clock Clock Pin Number ─┻─ Goes to the DS1307 SCL Pin. │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
}}
CON
#1, Disk_IO_Error, Clock_IO_Error
#3, File_System_Corrupted, File_System_Unsupported, File_System_Unmounted
#6, File_Not_Found, Directory_Not_Found, File_Or_Directory_Not_Found
#9, File_Already_Exist, Directroy_Already_Exist
#11, Directory_Is_Full, Disk_Is_Full
#13, Directory_Not_Empty, File_Is_Read_Only
#15, Checksum_Error, Reboot_error
#2_147_483_136, Max_File_Size, #65_535, Max_Directory_Size
OBJ
'rtc: "DS1307_RTCEngine.spin"
VAR
long diskSignature, volumeIdentification, partitionStart, partitionSize, FATSectorSize, FATType
long freeClusterCount, nextFreeCluster, firstDataSector, countOfClusters, hiddenSectors, rootCluster
long currentCluster, currentByte, currentSector, previousCluster, previousByte, previousSector
long currentDirectory, currentFile, filePosition, fileSize
word currentTime, currentDate, reservedSectorCount, externalFlags
word rootDirectorySectors, rootDirectorySectorNumber, fileSystemInfo, backupBootSector
byte partitionMountedFlag, errorNumberFlag, fileOpenCloseFlag, fileReadWriteFlag
byte sectorsPerCluster, numberOfFATs, mediaType, reservedByWindowsNT, cleanShutdown, hardError
byte workingDirectoryPathIndex, workingDirectoryTooDeep
byte dataBlock[512], directoryEntry[32], directoryEntryName[12], workingDirectoryPath[66]
byte volumeLabel[12], cardUniqueIDCopy[17]
byte unformatedNameBuffer[13], formatedNameBuffer[12]
PUB readByte '' 28 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Reads a byte from the file that is currently open and advances the position by one. │
'' │ │
'' │ This function will do nothing if a file is not currently open or if a partition is not currently mounted. │
'' │ │
'' │ If an error occurs this function will abort and return a pointer to a string describing that error. │
'' │ │
'' │ Returns the next byte to read from the file. At the end of file returns the last byte in the file repeatedly. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
readData(@result, 1)
PUB readShort '' 28 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Reads a short from the file that is currently open and advances the position by two. │
'' │ │
'' │ This function will do nothing if a file is not currently open or if a partition is not currently mounted. │
'' │ │
'' │ If an error occurs this function will abort and return a pointer to a string describing that error. │
'' │ │
'' │ Returns the next short to read from the file. At the end of file returns the last byte in the file repeatedly. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
readData(@result, 2)
PUB readLong '' 28 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Reads a long from the file that is currently open and advances the position by four. │
'' │ │
'' │ This function will do nothing if a file is not currently open or if a partition is not currently mounted. │
'' │ │
'' │ If an error occurs this function will abort and return a pointer to a string describing that error. │
'' │ │
'' │ Returns the next long to read from the file. At the end of file returns the last byte in the file repeatedly. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
readData(@result, 4)
PUB writeByte(value) '' 29 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Writes a byte to the file that is currently open and advances the position by one. │
'' │ │
'' │ This function will do nothing if a file is not currently open or if a partition is not currently mounted. │
'' │ │
'' │ If an error occurs this function will abort and return a pointer to a string describing that error. │
'' │ │
'' │ Additionally this function will do nothing if the currently open file is not open for writing. │
'' │ │
'' │ The maximum file size is 2,147,483,136 bytes. Exceeding this causes an error. │
'' │ │
'' │ Value - A byte to write to the file. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
writeData(@value, 1)
PUB writeShort(value) '' 29 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Writes a short to the file that is currently open and advances the position by two. │
'' │ │
'' │ This function will do nothing if a file is not currently open or if a partition is not currently mounted. │
'' │ │
'' │ If an error occurs this function will abort and return a pointer to a string describing that error. │
'' │ │
'' │ Additionally this function will do nothing if the currently open file is not open for writing. │
'' │ │
'' │ The maximum file size is 2,147,483,136 bytes. Exceeding this causes an error. │
'' │ │
'' │ Value - A short to write to the file. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
writeData(@value, 2)
PUB writeLong(value) '' 29 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Writes a long to the file that is currently open and advances the position by four. │
'' │ │
'' │ This function will do nothing if a file is not currently open or if a partition is not currently mounted. │
'' │ │
'' │ If an error occurs this function will abort and return a pointer to a string describing that error. │
'' │ │
'' │ Additionally this function will do nothing if the currently open file is not open for writing. │
'' │ │
'' │ The maximum file size is 2,147,483,136 bytes. Exceeding this causes an error. │
'' │ │
'' │ Value - A long to write to the file. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
writeData(@value, 4)
PUB writeString(stringPointer) '' 29 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Writes a string to the file that is currently open and advances the position by the string length. │
'' │ │
'' │ This function will do nothing if a file is not currently open or if a partition is not currently mounted. │
'' │ │
'' │ If an error occurs this function will abort and return a pointer to a string describing that error. │
'' │ │
'' │ Additionally this function will do nothing if the currently open file is not open for writing. │
'' │ │
'' │ The maximum file size is 2,147,483,136 bytes. Exceeding this causes an error. │
'' │ │
'' │ StringPointer - A pointer to a string to write to the file. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
writeData(stringPointer, strsize(stringPointer))
PUB readData(addressToPut, count) | index '' 25 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Reads data from the file that is currently open and advances the position by that amount of data. │
'' │ │
'' │ This function will do nothing if a file is not currently open or if a partition is not currently mounted. │
'' │ │
'' │ If an error occurs this function will abort and return a pointer to a string describing that error. │
'' │ │
'' │ AddressToPut - A pointer to the start of a data buffer to fill from disk. │
'' │ Count - The amount of data to read from disk. The data buffer must be at least this large. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
count #>= 0
repeat while((count > 0) and partitionMountedFlag and fileOpenCloseFlag)
if((currentByte >> 9) <> (filePosition >> 9))
flushData
currentByte := filePosition
ifnot(readWriteCurrentCluster("R", "F"))
quit
index := (filePosition & $1FF)
result := (count <# (512 - index))
bytemove(addressToPut, @dataBlock[index], result)
count -= result
addressToPut += result
filePosition := ((filePosition + result) <# ((fileSize - 1) #> 0))
PUB writeData(addressToGet, count) | index '' 25 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Writes data to the file that is currently open and advances the position by that amount of data. │
'' │ │
'' │ This function will do nothing if a file is not currently open or if a partition is not currently mounted. │
'' │ │
'' │ If an error occurs this function will abort and return a pointer to a string describing that error. │
'' │ │
'' │ Additionally this function will do nothing if the currently open file is not open for writing. │
'' │ │
'' │ The maximum file size is 2,147,483,136 bytes. Exceeding this causes an error. │
'' │ │
'' │ AddressToGet - A pointer to the start of a data buffer to write to disk. │
'' │ Count - The amount of data to write to disk. The data buffer must be at least this large. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
count #>= 0
repeat while((count > 0) and partitionMountedFlag and fileOpenCloseFlag and fileReadWriteFlag)
if((currentByte >> 9) <> (filePosition >> 9))
flushData
currentByte := filePosition
ifnot(readWriteCurrentCluster("W", "F"))
quit
index := (filePosition & $1FF)
result := (count <# (512 - index))
bytemove(@dataBlock[index], addressToGet, result)
count -= result
addressToGet += result
filePosition += result
fileSize #>= filePosition
PUB flushData '' 12 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Writes buffered data to disk. All file writes are buffered and are not written to disk immediantly. │
'' │ │
'' │ This function will do nothing if a file is not currently open or if a partition is not currently mounted. │
'' │ │
'' │ Additionally this function will do nothing if the currently open file is not open for writing. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag and fileOpenCloseFlag and fileReadWriteFlag)
readWriteCurrentSector("W")
PUB checkFilePosition '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Returns the current byte position within a file for reading and writing. │
'' │ │
'' │ This function will do nothing if a file is not currently open or if a partition is not currently mounted. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
return (filePosition & (partitionMountedFlag and fileOpenCloseFlag))
PUB changeFilePosition(position) | backupPosition '' 17 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Changes the current byte position within a file for reading and writing. │
'' │ │
'' │ This function will do nothing if a file is not currently open or if a partition is not currently mounted. │
'' │ │
'' │ Position - A byte position in the file. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag and fileOpenCloseFlag)
position := ((position <# (fileSize - 1)) #> 0)
backupPosition := position
position >>= 9
if(position <> currentSector)
flushData
position /= sectorsPerCluster
currentSector /= sectorsPerCluster
if(position <> currentSector)
if(position < currentSector)
currentCluster := currentFile
repeat until(position == result++)
readWriteFATBlock(currentCluster, "R")
currentCluster := readFATEntry(currentCluster)
if((currentCluster =< 1) or (FATEndOfClusterValue =< currentCluster))
partitionMountedFlag := false
errorNumberFlag := File_System_Corrupted
abort @FSCorrupted
result := true
filePosition := backupPosition
currentByte := backupPosition
if(result)
readWriteCurrentSector("R")
PUB closeFile '' 15 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Closes the currently open file in the current directory. │
'' │ │
'' │ All files opened for writing or appending must be closed or they will become corrupted and must be fixed with scandisk. │
'' │ │
'' │ If the partition is not mounted or an error occurs this function will abort and return a string describing that error. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
flushData
if(partitionMountedFlag and fileOpenCloseFlag~)
currentByte := previousByte
currentSector := previousSector
currentCluster := previousCluster
readWriteCurrentSector("R")
wordToBlock((currentByte + 18), readClock)
if(fileReadWriteFlag)
wordToBlock((currentByte + 22), currentTime)
wordToBlock((currentByte + 24), currentDate)
longToBlock((currentByte + 28), fileSize)
dataBlock[(currentByte + 11) & $1FF] |= $20
readWriteCurrentSector("W")
listReset
PUB openFile(fileName, mode) '' 33 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Opens a file in the current directory for reading, writing, or appending. │
'' │ │
'' │ All files opened for writing or appending must be closed or they will become corrupted and must be fixed with scandisk. │
'' │ │
'' │ If the partition is not mounted or an error occurs this function will abort and return a string describing that error. │
'' │ │
'' │ The "." and ".." entries are ignored by this function. │
'' │ │
'' │ Returns a pointer to the name of the opened file. │
'' │ │
'' │ FileName - The name of the file to open for reading, writing or appending. │
'' │ Mode - A character specifing the mode to use. R-Read, W-Write, A-Append. By default the file is opended for reading. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
result := unformatName(listFind(formatName(fileName), @fileNotFound))
if(listIsDirectory)
errorNumberFlag := File_Not_Found
abort @fileNotFound
currentFile := listCluster
ifnot(currentFile)
currentFile := createClusterChain(0)
readWriteCurrentSector("R")
wordToBlock((currentByte + 18), readClock)
wordToBlock((currentByte + 22), currentTime)
wordToBlock((currentByte + 24), currentDate)
dataBlock[(currentByte + 11) & $1FF] |= $20
wordToBlock((currentByte + 26), (currentFile & $FFFF))
wordToBlock((currentByte + 20), (currentFile >> 16))
readWriteCurrentSector("W")
fileReadWriteFlag := (findByte(mode, "w", "W") | findByte(mode, "a", "A"))
if(listIsReadOnly and fileReadWriteFlag)
errorNumberFlag := File_Is_Read_Only
abort @fileIsReadOnly
filePosition := 0
fileSize := listSize
previousByte := currentByte~
previousSector := currentSector~
previousCluster := currentCluster
currentCluster := currentFile
readWriteCurrentSector("R")
fileOpenCloseFlag := true
if(findByte(mode, "a", "A"))
changeFilePosition(fileSize)
filePosition := fileSize
PUB newFile(fileName) '' 40 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Creates a new file in the current directory. │
'' │ │
'' │ If the partition is not mounted or an error occurs this function will abort and return a string describing that error. │
'' │ │
'' │ Returns a pointer to the name of the file. List functions are not valid after calling this function. │
'' │ │
'' │ FileName - The name of the new file to create. Must be a new unique name in the current directory. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
return unformatName(listNew(formatName(fileName), $20, readClock, currentTime, 0, "F"))
listReset
PUB newDirectory(directoryName) '' 40 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Creates a new directory in the current directory. │
'' │ │
'' │ If the partition is not mounted or an error occurs this function will abort and return a string describing that error. │
'' │ │
'' │ Returns a pointer to the name of the directory. List functions are not valid after calling this function. │
'' │ │
'' │ DirectoryName - The name of the new directory to create. Must be a new unique name in the current directory. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
result := unformatName(listNew(formatName(directoryName), $30, readClock, currentTime, 0, "D"))
directoryName := currentDirectory
currentDirectory := currentFile
listNew(@dot, $10, currentDate, currentTime, currentDirectory, "F")
listNew(@dotdot, $10, currentDate, currentTime, directoryName, "F")
currentDirectory := directoryName
listReset
PUB deleteEntry(entryName) '' 32 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Deletes a file or directory in the current directory. Cannot delete non empty directories. │
'' │ │
'' │ If the partition is not mounted or an error occurs this function will abort and return a string describing that error. │
'' │ │
'' │ This function will throw an error if it is called on a read only file or directory. │
'' │ │
'' │ The "." and ".." entries are ignored by this function. │
'' │ │
'' │ Returns a pointer to the name of the deleted file or directory. List functions are not valid after calling this function.│
'' │ │
'' │ EntryName - The name of the file or directory to delete. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
result := unformatName(listFind(formatName(entryName), @fileOrDirectoryNotFound))
if(listIsReadOnly)
errorNumberFlag := File_Is_Read_Only
abort @fileIsReadOnly
currentFile := listCluster
if(listIsDirectory)
previousByte := currentByte~
previousSector := currentSector~
previousCluster := currentCluster
currentCluster := currentFile
repeat
entryName := listDirectory("R")
ifnot(entryName)
quit
ifnot(strcomp(entryName, @dot) or strcomp(entryName, @dotdot))
errorNumberFlag := Directory_Not_Empty
abort string("Directory Not Empty")
currentByte := previousByte
currentSector := previousSector
currentCluster := previousCluster
readWriteCurrentSector("R")
byteToBlock(currentByte, $E5)
readWriteCurrentSector("W")
destroyClusterChain(currentFile)
listReset
PUB renameEntry(entryNameToChange, entryNameToChangeTo) '' 33 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Renames a file or directory in the current directory. The new name must be unique in the current directory. │
'' │ │
'' │ If the partition is not mounted or an error occurs this function will abort and return a string describing that error. │
'' │ │
'' │ This function will throw an error if it is called on a read only file or directory. │
'' │ │
'' │ The "." and ".." entries are ignored by this function. │
'' │ │
'' │ Returns a pointer to the old name of the file or directory. List functions are not valid after calling this function. │
'' │ │
'' │ EntryNameToChange - The name of the file or directory to change. │
'' │ EntryNameToChangeTo - The name of the file or directory to change to. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
listDuplicate(formatName(entryNameToChangeTo))
result := unformatName(listFind(formatName(entryNameToChange), @fileOrDirectoryNotFound))
if(listIsReadOnly)
errorNumberFlag := File_Is_Read_Only
abort @fileIsReadOnly
bytemove(@dataBlock[currentByte & $1FF], formatName(entryNameToChangeTo), 11)
wordToBlock((currentByte + 18), readClock)
wordToBlock((currentByte + 22), currentTime)
wordToBlock((currentByte + 24), currentDate)
dataBlock[(currentByte + 11) & $1FF] |= $20
readWriteCurrentSector("W")
listReset
PUB changeAttributes(entryName, newAttributes) '' 33 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Changes the attributes of a file or directory in the current directory. │
'' │ │
'' │ If the partition is not mounted or an error occurs this function will abort and return a string describing that error. │
'' │ │
'' │ The "." and ".." entries are ignored by this function. │
'' │ │
'' │ Returns a pointer to the name of the file or directory. List functions are not valid after calling this function. │
'' │ │
'' │ EntryName - The name of the file or directory to change the attributes of. │
'' │ NewAttributes - A string of characters containing the new set of attributes. A-Archive, S-System, H-Hidden, R-Read Only. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
result := unformatName(listFind(formatName(entryName), @fileOrDirectoryNotFound))
wordToBlock((currentByte + 18), readClock)
wordToBlock((currentByte + 22), currentTime)
wordToBlock((currentByte + 24), currentDate)
byteToBlock((currentByte + 11), ( (listIsDirectory & $10) {
} | ($20 & findCharacter(newAttributes, "A")) {
} | ($04 & findCharacter(newAttributes, "S")) {
} | ($02 & findCharacter(newAttributes, "H")) {
} | ($01 & findCharacter(newAttributes, "R")) ) )
readWriteCurrentSector("W")
listReset
PUB changeDirectory(directoryName) '' 32 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Searches the current directory for the specified directory and enters that directory. │
'' │ │
'' │ Use "\" to return the root directory. Directory names and or paths following the "\" are not evaluated. │
'' │ │
'' │ If the partition is not mounted or an error occurs this function will abort and return a string describing that error. │
'' │ │
'' │ Returns a pointer to the working directory path string. List functions are not valid after calling this function. │
'' │ │
'' │ DirectoryName - The name of the directory to search for in the current directory and enter into. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(findByte(byte[directoryName], "/", "\"))
bytemove(@workingDirectoryPath, string("\"), 2)
workingDirectoryPathindex := 1
workingDirectoryTooDeep := false
currentDirectory := 0
elseifnot((byte[directoryName] == ".") and (not(byte[directoryName + 1])))
result := formatName(directoryName)
if((byte[directoryName] == ".") and (byte[directoryName + 1] == ".") and (not(byte[directoryName + 2])))
result := @dotDot
directoryName := unformatName(listFind(result, @directoryNotFound))
ifnot(listIsDirectory)
errorNumberFlag := Directory_Not_Found
abort @directoryNotFound
ifnot(workingDirectoryTooDeep)
if(result == @dotDot)
repeat until(workingDirectoryPath[--workingDirectoryPathIndex - 1] == "\")
workingDirectoryPath[workingDirectoryPathIndex] := 0
else
if(workingDirectoryPathIndex == 65)
workingDirectoryTooDeep := true
repeat strsize(directoryName)
if((workingDirectoryPathIndex => 64) or (byte[directoryName] == " "))
quit
workingDirectoryPath[workingDirectoryPathIndex++] := byte[directoryName++]
if(workingDirectoryPathIndex =< 64)
workingDirectoryPath[workingDirectoryPathIndex++] := "\"
workingDirectoryPath[workingDirectoryPathIndex] := 0
if(workingDirectoryTooDeep)
bytemove(@workingDirectoryPath, string("\\..."), 6)
currentDirectory := listCluster
listReset
return listWorkingDirectory
PUB listWorkingDirectory '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Returns a pointer to the working directory path string. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return @workingDirectoryPath
PUB listName(fromTheTop) '' 27 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Returns a pointer to the name of the next file or directory string in the current directory. │
'' │ │
'' │ After the last file or directory in the current directory is listed "listName" returns 0. │
'' │ │
'' │ After returning zero the next call to "listName" will start from the top of the current directory. │
'' │ │
'' │ If the partition is not mounted or an error occurs this function will abort and return a string describing that error. │
'' │ │
'' │ FromTheTop - If "T" "listName returns 0 and next call to "listName" will start from the top of the current directory. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
ifnot(findByte(fromTheTop, "t", "T"))
return unformatName(listDirectory("R"))
closeFile
PUB listSize '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Gets the size of current file or directory pointed to by "listName". Directories have no size. │
'' │ │
'' │ If a file is currently open this function will retrieve that files information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns the size of the file or directory in bytes. Maximum file size is 2,147,483,136 bytes. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return ( ( (directoryEntry[28] {
} | (directoryEntry[29] << 08) {
} | (directoryEntry[30] << 16) {
} | (directoryEntry[31] << 24) ) {
} <# $7FFFFE00) #> 0)
PUB listCreationDay '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Gets the creation day of the current file or directory pointed to by "listName". │
'' │ │
'' │ If a file is currently open this function will retrieve that file's information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns the creation day of the file or directory. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return (directoryEntry[16] & $1F)
PUB listCreationMonth '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Gets the creation month of the current file or directory pointed to by "listName". │
'' │ │
'' │ If a file is currently open this function will retrieve that file's information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns the creation month of the file or directory. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return (((directoryEntry[17] & $1) << 3) | (directoryEntry[16] >> 5))
PUB listCreationYear '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Gets the creation year of the current file or directory pointed to by "listName". │
'' │ │
'' │ If a file is currently open this function will retrieve that file's information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns the creation year of the file or directory. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return ((directoryEntry[17] >> 1) + 1980)
PUB listCreationCentiseconds '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Gets the creation centisecond of the current file or directory pointed to by "listName". │
'' │ │
'' │ If a file is currently open this function will retrieve that file's information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns the creation centisecond of the file or directory. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return (directoryEntry[13] // 100)
PUB listCreationSeconds '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Gets the creation second of the current file or directory pointed to by "listName". │
'' │ │
'' │ If a file is currently open this function will retrieve that file's information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns the creation second of the file or directory. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return (((directoryEntry[14] & $1F) << 1) + (directoryEntry[13] / 100))
PUB listCreationMinutes '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Gets the creation minute of the current file or directory pointed to by "listName". │
'' │ │
'' │ If a file is currently open this function will retrieve that file's information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns the creation minute of the file or directory. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return (((directoryEntry[15] & $7) << 3) | (directoryEntry[14] >> 5))
PUB listCreationHours '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Gets the creation hour of the current file or directory pointed to by "listName". │
'' │ │
'' │ If a file is currently open this function will retrieve that file's information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns the creation hour of the file or directory. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return (directoryEntry[15] >> 3)
PUB listAccessDay '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Gets the last day of access of the current file or directory pointed to by "listName". │
'' │ │
'' │ If a file is currently open this function will retrieve that file's information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns the last acess day of the file or directory. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return (directoryEntry[18] & $1F)
PUB listAccessMonth '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Gets the last month of access of the current file or directory pointed to by "listName". │
'' │ │
'' │ If a file is currently open this function will retrieve that file's information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns the last acess month of the file or directory. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return (((directoryEntry[19] & $1) << 3) | (directoryEntry[18] >> 5))
PUB listAccessYear '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Gets the last year of access of the current file or directory pointed to by "listName". │
'' │ │
'' │ If a file is currently open this function will retrieve that file's information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns the last acess year of the file or directory. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return ((directoryEntry[19] >> 1) + 1980)
PUB listModificationDay '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Gets the last day of modification of the current file or directory pointed to by "listName". │
'' │ │
'' │ If a file is currently open this function will retrieve that file's information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns the modification day of the file or directory. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return (directoryEntry[24] & $1F)
PUB listModificationMonth '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Gets the last month of modification of the current file or directory pointed to by "listName". │
'' │ │
'' │ If a file is currently open this function will retrieve that file's information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns the modification month of the file or directory. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return (((directoryEntry[25] & $1) << 3) | (directoryEntry[24] >> 5))
PUB listModificationYear '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Gets the last year of modification of the current file or directory pointed to by "listName". │
'' │ │
'' │ If a file is currently open this function will retrieve that file's information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns the modification year of the file or directory. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return ((directoryEntry[25] >> 1) + 1980)
PUB listModificationSeconds '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Gets the last second of modification of the current file or directory pointed to by "listName". │
'' │ │
'' │ If a file is currently open this function will retrieve that file's information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns the modification second of the file or directory. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return ((directoryEntry[22] & $1F) << 1)
PUB listModificationMinutes '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Gets the last minute of modification of the current file or directory pointed to by "listName". │
'' │ │
'' │ If a file is currently open this function will retrieve that file's information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns the modification minute of the file or directory. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return (((directoryEntry[23] & $7) << 3) | (directoryEntry[22] >> 5))
PUB listModificationHours '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Gets the last hour of modification of the current file or directory pointed to by "listName". │
'' │ │
'' │ If a file is currently open this function will retrieve that file's information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns the modification hour of the file or directory. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return (directoryEntry[23] >> 3)
PUB listIsReadOnly '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Returns whether or not the current file or directory pointed to by "listName" is read only. │
'' │ │
'' │ If a file is currently open this function will retrieve that file's information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns true or false. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
result or= (directoryEntry[11] & $1)
PUB listIsHidden '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Returns whether or not the current file or directory pointed to by "listName" is hidden. │
'' │ │
'' │ If a file is currently open this function will retrieve that file's information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns true or false. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
result or= (directoryEntry[11] & $2)
PUB listIsSystem '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Returns whether or not the current file or directory pointed to by "listName" is a system file. │
'' │ │
'' │ If a file is currently open this function will retrieve that file's information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns true or false. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
result or= (directoryEntry[11] & $4)
PUB listIsDirectory '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Returns whether or not the current file or directory pointed to by "listName" is a directory. │
'' │ │
'' │ If a file is currently open this function will retrieve that file's information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns true or false. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
result or= (directoryEntry[11] & $10)
PUB listIsArchive '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Returns whether or not the current file or directory pointed to by "listName" has been modified since the last backup. │
'' │ │
'' │ If a file is currently open this function will retrieve that file's information. │
'' │ │
'' │ If "listName" did not succed or was not previously called the value returned is invalid. │
'' │ │
'' │ Returns true or false. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
result or= (directoryEntry[11] & $20)
PUB checkCountOfClusters '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Returns the count of clusters for the current partition. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return (countOfClusters - 1)
PUB checkTotalSectors '' 3 Stack longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Returns the count of sectors for the current partition. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return (partitionSize - firstDataSector)
PUB checkSectorsPerCluster '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Returns the count of sectors per cluster for the current partition. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return sectorsPerCluster
PUB checkBytesPerSector '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Returns the count of bytes per sector for the current partition. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return 512
PUB checkUsedSectorCount(mode) '' 18 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Returns the current used sector count on this partition. Will do nothing if the card is not mounted. │
'' │ │
'' │ If an error occurs this function will abort and return a pointer to a string describing that error. │
'' │ │
'' │ In fast mode this function will return the last valid used sector count if avialable. This is an estimate value. │
'' │ │
'' │ In slow mode this function will compute the used sector count by scanning the entire FAT. This can take a long time. │
'' │ │
'' │ One sector is equal to 512 bytes. Multiply the used sector count by 512 to determine the number of used bytes. │
'' │ │
'' │ This function also finds the next free cluster needed for creating and extending files and directories. │
'' │ │
'' │ Call this function when running out of disk space to find the next free cluster if available. │
'' │ │
'' │ This function can be called while a file is open to find more disk space. The file will still be open afterwards. │
'' │ │
'' │ If the last valid used sector count is not avialable when using fast mode this function will enter slow mode, │
'' │ │
'' │ Mode - A character specifing the mode to use. F-Fast, S-Slow. Default slow. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return (((countOfClusters - 1) * sectorsPerCluster) - checkFreeSectorCount(mode))
PUB checkFreeSectorCount(mode) '' 14 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Returns the current free sector count on this partition. Will do nothing if the card is not mounted. │
'' │ │
'' │ If an error occurs this function will abort and return a pointer to a string describing that error. │
'' │ │
'' │ In fast mode this function will return the last valid free sector count if avialable. This is an estimate value. │
'' │ │
'' │ In slow mode this function will compute the free sector count by scanning the entire FAT. This can take a long time. │
'' │ │
'' │ One sector is equal to 512 bytes. Multiply the free sector count by 512 to determine the number of free bytes. │
'' │ │
'' │ This function also finds the next free cluster needed for creating and extending files and directories. │
'' │ │
'' │ Call this function when running out of disk space to find the next free cluster if available. │
'' │ │
'' │ This function can be called while a file is open to find more disk space. The file will still be open afterwards. │
'' │ │
'' │ If the last valid free sector count is not avialable when using fast mode this function will enter slow mode, │
'' │ │
'' │ Mode - A character specifing the mode to use. F-Fast, S-Slow. Default slow. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
flushData
if(findByte(mode, "f", "F") and (freeClusterCount <> $FFFFFFFF))
result := freeClusterCount
else
repeat mode from 0 to countOfClusters
ifnot(FATEntryNumber(mode))
readWriteFATBlock(mode, "R")
result -= (not(readFATEntry(mode)))
ifnot(result)
nextFreeCluster := ((mode + 1) <# countOfClusters)
freeClusterCount := result
readWriteCurrentSector("R")
result *= sectorsPerCluster
PUB checkVolumeLabel '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Returns a pointer to the volume label string. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return @volumeLabel
PUB checkFileSystemType '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Returns a pointer to the file system type string. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
if(FATType)
return string("FAT32 ")
return string("FAT16 ")
PUB checkVolumeIdentification '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Returns the volume identification number. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return volumeIdentification
PUB checkDiskSignature '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Returns the disk signature number. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
if(partitionMountedFlag)
return diskSignature
PUB checkErrorNumber '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Returns true if the file system incurred an error and false if not. The true number returned is the error number. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
return errorNumberFlag~
PUB checkPartitionMounted '' 3 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Returns true if the file system is still mounted and false if not. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
result or= partitionMountedFlag
PUB bootPartition(fileName) | bootSectors[64] '' 101 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Reboots the propeller chip to run the selected valid spin BIN or EEPROM file. │
'' │ │
'' │ If an error occurs this function will abort and return a pointer to a string describing that error. │
'' │ │
'' │ FileName - The name of the file to reboot from in the current directory. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
longfill(@bootSectors, 0, 64)
openFile(fileName~, "R")
repeat (listSize <# 32768)
result += readByte
ifnot($1FF & fileName++)
bootSectors[fileName >> 9] := (partitionStart + FATFirstSectorInCluster(currentCluster) + FATWorkingSectorInCluster)
result &= $FF
changeFilePosition(6)
fileName := readShort
closeFile
if((result and (result <> $14)) or (fileName <> $10))
errorNumberFlag := Checksum_Error
abort string("Checksum Error")
unmountPartition
readWriteBlock(@bootSectors, "B")
partitionMountedFlag := false
errorNumberFlag := Reboot_Error
abort string("Reboot Error")
PUB formatPartition(partition, newVolumeLabel) '' 33 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Formats and mounts the specified partition. The file system may be a FAT16/32 file system with up to ~1TB of space. │
'' │ │
'' │ If an error occurs this function will abort and return a pointer to a string describing that error. │
'' │ │
'' │ Returns a pointer to the volume label. │
'' │ │
'' │ Parition - Partition number to mount (between 0 and 3). The default partition number is 0. │
'' │ VolumeLabel - New volume label for the partition. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
mountPartition(partition, "C")
listReset
repeat while(readWriteCurrentCluster("R", "D"))
bytefill(@dataBlock, 0, 512)
readWriteCurrentSector("W")
currentByte += 512
listNew(formatName(newVolumeLabel), $8, readClock, currentTime, 0, "F")
bytefill(@dataBlock, 0, 512)
repeat result from reservedSectorCount to (rootDirectorySectorNumber - 1)
readWriteBlock(result, "W")
readWriteFATBlock(0, "R")
writeFATEntry(0, ($0FFFFF00 | mediaType))
writeFATEntry(1, $0FFFFFFF)
readWriteFATBlock(0, "W")
if(FATType)
readWriteFATBlock(rootCluster, "R")
writeFATEntry(rootCluster, true)
readWriteFATBlock(rootCluster, "W")
listReset
return unformatName(listDirectory("V"))
PUB mountPartition(partition, checkDisk) '' 28 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Mounts the specified partition. The file system may be a FAT16/32 file system with up to ~1TB of space. │
'' │ │
'' │ If an error occurs this function will abort and return a pointer to a string describing that error. │
'' │ │
'' │ File sizes up to 2,147,483,136 bytes are supported. │
'' │ │
'' │ Directory sizes up to 65,535 entries are supported. │
'' │ │
'' │ Additionally the check disk flag can be raised to call scan disk on an improperly unmounted volume. │
'' │ │
'' │ Returns a pointer to the volume label. │
'' │ │
'' │ Parition - Partition number to mount (between 0 and 3). The default partition number is 0. │
'' │ CheckDisk - Raises the check disk flag. C-Raise Flag │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
unmountPartition
partitionStart := 0
readWriteBlock(0, "M")
bytemove(@cardUniqueIDCopy, @cardUniqueID, 17)
readWriteBlock(0, "R")
if(blockToWord(510) <> $AA55)
errorNumberFlag := File_System_Corrupted
abort @FSCorrupted
diskSignature := blockToLong(440)
partition := (((partition <# 3) #> 0) << 4)
if((blockToByte(0) <> $EB) and (blockToByte(0) <> $E9))
case(blockToByte(450 + partition) & $F)
$0 .. $3, $5, $7 .. $A, $D, $F:
errorNumberFlag := File_System_Unsupported
abort @FSUnsupported
partitionStart := blockToLong(454 + partition)
readWriteBlock(0, "R")
if(blockToWord(510) <> $AA55)
errorNumberFlag := File_System_Corrupted
abort @FSCorrupted
if(blockToWord(11) <> 512)
errorNumberFlag := File_System_Unsupported
abort @FSUnsupported
sectorsPerCluster := blockToByte(13)
reservedSectorCount := blockToWord(14)
numberOfFATs := blockToByte(16)
externalFlags := 0
mediaType := blockToByte(21)
hiddenSectors := blockToLong(28)
partitionSize := blockToWord(19)
ifnot(partitionSize)
partitionSize := blockToLong(32)
FATSectorSize := blockToWord(22)
ifnot(FATSectorSize)
FATSectorSize := blockToLong(36)
rootDirectorySectors := (blockToWord(17) >> 4)
rootDirectorySectorNumber := (reservedSectorCount + (numberOfFATs * FATSectorSize))
firstDataSector := (rootDirectorySectorNumber + rootDirectorySectors)
countOfClusters := ((partitionSize - firstDataSector) / sectorsPerCluster)
nextFreeCluster := 2
freeClusterCount := $FFFFFFFF
if(countOfClusters < 4085)
errorNumberFlag := File_System_Unsupported
abort @FSUnsupported
FATType := false
if(65525 =< countOfClusters++)
FATType := true
result := (28 & FATType)
reservedByWindowsNT := blockToByte(37 + result)
volumeIdentification := blockToLong(39 + result)
if(findByte(checkDisk, "c", "C"))
dataBlock[37 + partition] |= $3
readWriteBlock(0, "W")
if(FATType)
if(blockToWord(40) & $80)
numberOfFATs := 1
externalFlags := (blockToWord(40) & $F)
if(blockToWord(42))
errorNumberFlag := File_System_Unsupported
abort @FSUnsupported
rootCluster := blockToLong(44)
fileSystemInfo := blockToWord(48)
backupBootSector := blockToWord(50)
readWriteBlock(fileSystemInfo, "R")
if(blockToWord(510) <> $AA55)
errorNumberFlag := File_System_Corrupted
abort @FSCorrupted
freeClusterCount := blockToLong(488)
nextFreeCluster := blockToLong(492)
if(nextFreeCluster == $FFFFFFFF)
nextFreeCluster := 2
readWriteFATBlock(0, "R")
result := ($8000 << (12 & FATType))
cleanShutdown := (readFATEntry(1) & result)
hardError := (readFATEntry(1) & result)
bytemove(@workingDirectoryPath, string("\"), 2)
workingDirectoryPathIndex := 1
workingDirectoryTooDeep := false
currentDirectory := 0
partitionMountedFlag := true
listReset
result := listDirectory("V")
bytemove(@volumeLabel, result, 12)
PUB unmountPartition '' 18 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Unmounts the specified partition. │
'' │ │
'' │ If an error occurs this function will abort and return a pointer to a string describing that error. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
closeFile
errorNumberFlag := 0
if(partitionMountedFlag~)
readWriteBlock(0, "R")
dataBlock[37 + (28 & FATType)] &= $FC
readWriteBlock(0, "W")
if(FATType)
readWriteBlock(fileSystemInfo, "R")
longToBlock(freeClusterCount, 488)
longToBlock(nextFreeCluster, 492)
readWriteBlock(fileSystemInfo, "W")
PUB FATEngineStart(DOPin, CLKPin, DIPin, CSPin, RTCDATPin, RTCCLKPin) '' 17 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Starts up the SDC driver running on a cog and checks out a lock for the driver. │
'' │ │
'' │ Returns true on success and false on failure. │
'' │ │
'' │ DOPin - The SPI data out pin from the SD2.0 card. Between 0 and 31. │
'' │ CLKPin - The SPI clock pin from the SD2.0 card. Between 0 and 31. │
'' │ DIPin - The SPI data in pin from the SD2.0 card. Between 0 and 31. │
'' │ CSPin - The SPI chip select pin from the SD2.0 card. Between 0 and 31. │
'' │ RTCDATPin - The I2C data pin for the DS1307 real time clock module. Between 0 and 31. │
'' │ RTCCLKPin - The I2C clock pin for the DS1307 real time clock module. Between 0 and 31. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
FATEngineStop
readTimeout := (clkfreq / constant(8 * 8 * 10))
writeTimeout := (clkfreq / constant(8 * 8 * 4))
DOPin := ((DOPin <# 31) #> 0)
CLKPin := ((CLKPin <# 31) #> 0)
DIPin := ((DIPin <# 31) #> 0)
dataOutCounterSetup := (constant(%11000 << 26) + (CLKPin << 9) + DOPin)
clockCounterSetup := (constant(%00100 << 26) + CLKPin)
dataInCounterSetup := (constant(%00100 << 26) + DIPin)
dataOutPin := (| 0))
CBAAddress := @cardBlockAddress
CSAAddress := @cardSectorAddress
CSCAddress := @cardSectorCount
CCFAddress := @cardCommandFlag
CEFAddress := @cardErrorFlag
CUIAddress := @cardUniqueID
if((dataOutPin <> clockPin) and (dataOutPin <> dataInPin) and (dataOutPin <> chipSelectPin) and {
}(clockPin <> dataInPin) and (clockPin <> chipSelectPin) and (dataInPin <> chipSelectPin) and {
}{rtc.RTCEngineStart(RTCDATPin, RTCCLKPin) and} (clkmode > 1) and (chipver == 1))
cardLockID := locknew
if(++cardLockID)
cardCogID := cognew(@initialization, 0)
if(++cardCogID)
return true
FATEngineStop
PUB FATEngineStop '' 6 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Shuts down the SDC driver running on a cog and returns the lock used by the driver. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
bytemove(@cardUniqueID, 0, 17)
'rtc.RTCEngineStop
if(cardCogID)
cogstop(-1 + cardCogID~)
if(cardLockID)
lockret(-1 + cardLockID~)
PRI listDuplicate(entryName) ' 27 Stack Longs
closeFile
repeat
result := listDirectory("R")
if(strcomp(result, entryName))
if(listIsDirectory)
errorNumberFlag := Directroy_Already_Exist
abort string("Directory Already Exist")
errorNumberFlag := File_Already_Exist
abort string("File Already Exist")
while(result)
PRI listFind(entryName, errorMessage) ' 28 Stack Longs
closeFile
repeat
result := listDirectory("R")
ifnot(result)
errorNumberFlag := File_Or_Directory_Not_Found
abort errorMessage
until(strcomp(entryName, result))
currentByte -= 32
PRI listNew(entryName, entryAttributes, entryDate, entryTime, entryCluster, entryType) ' 36 Stack Longs
listDuplicate(entryName)
repeat while(readWriteCurrentCluster("W", "D"))
if((blockToByte(currentByte) <> $E5) and blockToByte(currentByte))
currentByte += 32
else
if(entryType == "D")
entryCluster := createClusterChain(0)
currentFile := entryCluster
readWriteCurrentSector("R")
bytefill(@dataBlock[currentByte & $1FF], 0, 32)
bytemove(@dataBlock[currentByte & $1FF], entryName, 11)
byteToBlock((currentByte + 11), entryAttributes)
wordToBlock((currentByte + 14), entryTime)
wordToBlock((currentByte + 16), entryDate)
wordToBlock((currentByte + 18), entryDate)
wordToBlock((currentByte + 22), entryTime)
wordToBlock((currentByte + 24), entryDate)
wordToBlock((currentByte + 26), (entryCluster & $FFFF))
wordToBlock((currentByte + 20), (entryCluster >> 16))
readWriteCurrentSector("W")
return entryName
errorNumberFlag := Directory_Is_Full
abort string("Directory Is Full")
PRI listDirectory(volumeIDAttribute) ' 23 Stack Longs
if(fileOpenCloseFlag)
closeFile
repeat while(readWriteCurrentCluster("R", "D") and blockToByte(currentByte))
if((blockToByte(currentByte) == $E5) or (((volumeIDAttribute == "V") ^ blockToByte(currentByte + 11)) & $8))
currentByte += 32
else
bytemove(@directoryEntry, @dataBlock[currentByte & $1FF], 32)
bytemove(@directoryEntryName, @directoryEntry, 11)
if(directoryEntryName == $5)
directoryEntryName := $E5
currentByte += 32
return @directoryEntryName
listReset
PRI listCluster ' 3 Stack Longs
result := (directoryEntry[26] + (directoryEntry[27] << 8) + (directoryEntry[20] << 16) + (directoryEntry[21] << 24))
if((result == 1) or (FATEndOfClusterValue =< result))
partitionMountedFlag := false
errorNumberFlag := File_System_Corrupted
abort @FSCorrupted
PRI listReset '' 3 Stack Longs
currentByte := 0
currentSector := 0
currentCluster := currentDirectory
bytefill(@directoryEntry, 0, 32)
bytefill(@directoryEntryName, 0, 12)
PRI readWriteCurrentCluster(readWrite, fileOrDirectory) ' 19 Stack Longs
ifnot(partitionMountedFlag)
errorNumberFlag := File_System_Unmounted
abort string("File System Unmounted")
ifnot(currentByte & $1FF)
if(fileOpenCloseFlag and (currentByte => $7FFFFE00))
return false
ifnot(fileOpenCloseFlag or (currentByte < constant(65536 * 32)))
return false
ifnot(currentcluster or FATType or ((currentByte >> 9) < rootDirectorySectors))
return false
if(((currentByte >> 9) <> currentSector) and (not(FATWorkingSectorInCluster)) and (FATType or currentcluster))
result := currentCluster
ifnot(result)
result := rootCluster
readWriteFATBlock(result, "R")
fileOrDirectory := readFATEntry(result)
if(fileOrDirectory =< 1)
partitionMountedFlag := false
errorNumberFlag := File_System_Corrupted
abort @FSCorrupted
if(fileOrDirectory => FATEndOfClusterValue)
if(readWrite == "R")
return false
fileOrDirectory := createClusterChain(result)
currentCluster := fileOrDirectory
readWriteCurrentSector("R")
return true
PRI readWriteCurrentSector(readWrite) ' 9 Stack Longs
result := FATFirstSectorInCluster(currentCluster) + FATWorkingSectorInCluster
ifnot(currentCluster)
result := rootDirectorySectorNumber + (currentByte >> 9)
if(FATType)
result := FATFirstSectorInCluster(rootCluster) + FATWorkingSectorInCluster
readWriteBlock(result, readWrite)
if(readWrite == "R")
currentSector := (currentByte >> 9)
PRI findByte(byteToCompare, thisByte, thatByte) ' 6 Stack Longs
if((byteToCompare == thisByte) or (byteToCompare == thatByte))
return true
PRI findCharacter(charactersToSearch, characterToFind) | convertedCharacter ' 6 Stack Longs
repeat strsize(charactersToSearch)
convertedCharacter := byte[charactersToSearch++]
case convertedCharacter
"a" .. "z": convertedCharacter -= 32
if(convertedCharacter == characterToFind)
return true
PUB unformatName(name) ' 4 Stack Longs
if(name)
unformatedNameBuffer[12] := 0
bytefill(@unformatedNameBuffer, " ", 12)
bytemove(@unformatedNameBuffer, name, 8)
repeat while(unformatedNameBuffer[++result] <> " ")
unformatedNameBuffer[result++] := "."
bytemove(@unformatedNameBuffer[result], @byte[name][8], 3)
if(unformatedNameBuffer[result] == " ")
unformatedNameBuffer[--result] := " "
return @unformatedNameBuffer
PUB formatName(name) ' 4 Stack Longs
formatedNameBuffer[11] := 0
bytefill(@formatedNameBuffer, " ", 11)
repeat strsize(name--)
if(byte[++name] == ".")
result := 0
repeat strsize(++name)
if((result < 3) and (byte[name] > 31))
formatedNameBuffer[8 + result++] := byte[name++]
quit
if((result < 8) and (byte[name] > 31))
formatedNameBuffer[result++] := byte[name]
repeat result from 0 to 10
case formatedNameBuffer[result]
"a" .. "z": formatedNameBuffer[result] -= 32
$22, "*" .. ",", "." .. "/", ":" .. "?", "[" .. "]", "|", $7F: formatedNameBuffer[result] := "_"
if(formatedNameBuffer == " ")
formatedNameBuffer := "_"
if(formatedNameBuffer == $E5)
formatedNameBuffer := $5
return @formatedNameBuffer
PRI createClusterChain(clusterToLink) ' 14 Stack Longs
readWriteFATBlock(nextFreeCluster, "R")
repeat result from nextFreeCluster to countOfClusters
ifnot(FATEntryNumber(result))
readWriteFATBlock(result, "R")
ifnot(readFATEntry(result))
writeFATEntry(result, true)
readWriteFATBlock(result, "W")
nextFreeCluster := ((result + 1) <# countOfClusters)
if(clusterToLink)
readWriteFATBlock(clusterToLink, "R")
writeFATEntry(clusterToLink, result)
readWriteFATBlock(clusterToLink, "W")
bytefill(@dataBlock, 0, 512)
repeat clusterToLink from 0 to (sectorsPerCluster - 1)
readWriteBlock((FATFirstSectorInCluster(result) + clusterToLink), "W")
quit
if(result => countOfClusters)
nextFreeCluster := 2
errorNumberFlag := Disk_Is_Full
abort string("Disk Is Full")
PRI destroyClusterChain(clusterToDestroy) ' 14 Stack Longs
repeat while((1 < clusterToDestroy) and (clusterToDestroy < FATEndOfClusterValue))
ifnot(result and (FATBlockNumber(result) == FATBlockNumber(clusterToDestroy)))
readWriteFATBlock(clusterToDestroy, "R")
result := clusterToDestroy
clusterToDestroy := readFATEntry(clusterToDestroy)
writeFATEntry(result, false)
if(FATBlockNumber(result) <> FATBlockNumber(clusterToDestroy))
readWriteFATBlock(result, "W")
if(result)
readWriteFATBlock(result, "W")
PRI readFATEntry(cluster) ' 8 Stack Longs
cluster := FATEntryNumber(cluster)
ifnot(FATType)
return blockToWord(cluster)
return (blockTolong(cluster) & $0FFFFFFF)
PRI writeFATEntry(cluster, value) ' 10 Stack Longs
cluster := FATEntryNumber(cluster)
ifnot(FATType)
wordToBlock(cluster, value)
else
longToBlock(cluster, ((value & $0FFFFFFF) | (blockTolong(cluster) & $F0000000)))
PRI readWriteFATBlock(cluster, readWrite) ' 10 Stack Longs
cluster := FATBlockNumber(cluster)
result := externalFlags
repeat ((numberOfFATs & (readWrite == "W")) | (-(readWrite == "R")))
readWriteBlock((reservedSectorCount + cluster + (FATSectorSize * result++)), readWrite)
PRI FATBlockNumber(cluster) ' 4 Stack Longs
return (cluster >> (8 + FATType))
PRI FATEntryNumber(cluster) ' 4 Stack Longs
return ((cluster & ($FF >> (-FATType))) << (1 - FATType))
PRI FATEndOfClusterValue ' 3 Stack Longs
return ($FFF0 | (FATType & $0FFFFFF0))
PRI FATWorkingSectorInCluster ' 3 Stack Longs
return ((currentByte >> 9) // sectorsPerCluster)
PRI FATFirstSectorInCluster(cluster) ' 4 Stack Longs
return (((cluster - 2) * sectorsPerCluster) + firstDataSector)
PRI blockToLong(index) ' 4 Stack Longs
bytemove(@result, @dataBlock[(index & $1FF) <# 508], 4)
PRI blockToWord(index) ' 4 Stack Longs
bytemove(@result, @dataBlock[(index & $1FF) <# 510], 2)
PRI blockToByte(index) ' 4 Stack Longs
return dataBlock[index & $1FF]
PRI longToBlock(index, value) ' 5 Stack Longs
bytemove(@dataBlock[(index & $1FF) <# 508], @value, 4)
PRI wordToBlock(index, value) ' 5 Stack Longs
bytemove(@dataBlock[(index & $1FF) <# 510], @value, 2)
PRI byteToBlock(index, value) ' 5 Stack Longs
dataBlock[index & $1FF] := value
PRI readClock ' 3 + 11 Stack Longs
'ifnot(rtc.checkTime)
' errorNumberFlag := Clock_IO_Error
' abort string("Clock I/O Error")
currentTime := 0 '((rtc.checkSecond >> 1) | (rtc.checkMinute << 5) | (rtc.checkHour << 11))
currentDate := 0 '(rtc.checkDate | (rtc.checkMonth << 5) | ((rtc.checkYear - 1980) << 9))
return currentDate
PRI readWriteBlock(address, command) ' 5 Stack Longs
if(strcomp(@cardUniqueID, @cardUniqueIDCopy) or (command == "M") or (command == "T"))
repeat while(lockset(cardLockID - 1))
cardSectorAddress := ((address + (partitionStart & (command <> "B"))) & (command <> "T"))
cardBlockAddress := (@dataBlock & (command <> "B") & (command <> "T"))
cardCommandFlag := command
repeat while(cardCommandFlag)
command := cardErrorFlag~
lockclr(cardLockID - 1)
if(command)
partitionMountedFlag := false
errorNumberFlag := Disk_IO_Error
abort string("Disk I/O Error")
DAT
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' SDC Driver
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
org 0
' //////////////////////Initialization/////////////////////////////////////////////////////////////////////////////////////////
initialization mov ctra, clockCounterSetup ' Setup counter.
or outa, chipSelectPin ' Setup I/O Pins.
or outa, dataInPin '
or dira, chipSelectPin '
or dira, dataInPin '
or dira, clockPin '
mov cardCounter, fiveHundredAndTwelve ' Skip to instruction handle.
jmp #instructionWait '
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' Command Center
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
instructionRetry cmp cardBuffer, #"M" wc, wz ' Try at most 8 more times to mount card.
if_c cmp cardBuffer, #"F" wc, wz '
if_nc_or_z djnz cardBuffer, #mountCard '
cmp cardBuffer, #"R" wz ' Try at most twice to read the block.
if_z djnz cardBuffer, #readBlock '
cmp cardBuffer, #"W" wz ' Try at most twice to write the block.
if_z djnz cardBuffer, #writeBlock '
cmp cardBuffer, #"B" wz ' Reboot the chip if booting failure.
if_z mov buffer, #$80 '
if_z clkset buffer '
instructionError wrbyte maxPositiveInteger, CEFAddress ' Assert error flag and unmount card.
mov cardMounted, #0 '
' //////////////////////Instruction Handle/////////////////////////////////////////////////////////////////////////////////////
instructionLoop wrbyte fiveHundredAndTwelve, CCFAddress ' Finish up old command.
instructionWait rdbyte cardBuffer, CCFAddress ' Wait for a command to come.
test cardMounted, maxPositiveInteger wc '
cmp cardBuffer, #"B" wz ' If rebooting was requested do it.
if_z_and_nc jmp #instructionError '
if_z_and_c jmp #rebootChip '
cmp cardBuffer, #"R" wz ' If read block was requested do it.
if_z_and_nc jmp #instructionError '
if_z_and_c jmp #readBlock '
cmp cardBuffer, #"W" wz ' If write block was requested do it.
if_z_and_nc jmp #instructionError '
if_z_and_c jmp #writeBlock '
djnz cardCounter, #instructionSkip ' Poll the card every so often.
mov cardCounter, fiveHundredAndTwelve '
if_nc jmp #instructionSkip '
call #cardStatus '
instructionSkip cmp cardBuffer, #"T" wz ' If tristating was requested do it.
if_z xor dira, chipSelectPin '
if_z xor dira, dataInPin '
if_z xor dira, clockPin '
if_z test cardMounted, #$1 wc '
if_z_and_nc test cardMounted, #$2 wc '
if_z_and_c xor cardMounted, #$1 '
if_z jmp #instructionLoop '
cmp cardBuffer, #"M" wz ' If mounting was requested do it.
if_nz jmp #instructionWait '
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' Mount Card
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
mountCard mov SPITiming, #0 ' Setup SPI parameters.
mov counter, #128 ' Send out for more than 1 millisecond.
seventyFourClocks call #readSPI '
djnz counter, #seventyFourClocks '
' //////////////////////Go Idle State//////////////////////////////////////////////////////////////////////////////////////////
mov counter, #128 ' Setup counter to try a few times.
enterIdleStateLoop mov SPICommandOut, #($40 | 0) ' Send out command 0.
mov SPIParameterOut, #0 '
movs commandSPICRC, #$95 '
call #commandSPI '
call #shutdownSPI '
cmp SPIResponceIn, #1 wz ' Try a few times.
if_nz djnz counter, #enterIdleStateLoop '
tjz counter, #instructionRetry '
' //////////////////////Send Interface Condition///////////////////////////////////////////////////////////////////////////////
mov SPICommandOut, #($40 | 8) ' Send out command 8.
mov SPIParameterOut, #$1AA '
movs commandSPICRC, #$87 '
call #commandSPI '
call #longSPI '
call #shutdownSPI '
test SPIResponceIn, #$7E wz ' If failure goto SD 1.X initialization.
if_nz jmp #exitIdleState_SD '
and SPILongIn, #$1FF ' SD 2.0 initialization.
cmp SPILongIn, #$1AA wz '
if_nz jmp #instructionRetry '
' //////////////////////Send Operating Condition///////////////////////////////////////////////////////////////////////////////
exitIdleState_SD mov cardType, #0 ' Card type is MMC.
mov counter, #128 ' Setup counter to try a few times.
exitIdleStateLoop_SD mov SPICommandOut, #($40 | 55) ' Send out command 55.
mov SPIParameterOut, #0 '
call #commandSPI '
call #shutdownSPI '
test SPIResponceIn, #$7E wz ' If failure goto MMC initialization. '
if_nz jmp #exitIdleState_MMC '
mov SPICommandOut, #($40 | 41) ' Send out command 41 with HCS bit set.
mov SPIParameterOut, HCSBitMask '
call #commandSPI '
call #shutdownSPI '
cmp SPIResponceIn, #0 wz ' Try a few times.
if_nz djnz counter, #exitIdleStateLoop_SD '
tjz counter, #instructionRetry '
djnz cardType, #readOCR ' Card type SD.
' //////////////////////Send Operating Condition///////////////////////////////////////////////////////////////////////////////
exitIdleState_MMC mov counter, #128 ' Setup counter to try a few times.
exitIdleStateLoop_MMC mov SPICommandOut, #($40 | 1) ' Send out command 1.
mov SPIParameterOut, #0 '
call #commandSPI '
call #shutdownSPI '
cmp SPIResponceIn, #0 wz ' Try a few times.
if_nz djnz counter, #exitIdleStateLoop_MMC '
tjz counter, #instructionRetry '
' //////////////////////Read OCR Register//////////////////////////////////////////////////////////////////////////////////////
readOCR mov SPICommandOut, #($40 | 58) ' Ask the card for its OCR register.
mov SPIParameterOut, #0 '
call #commandSPI '
call #longSPI '
call #shutdownSPI '
tjnz SPIResponceIn, #instructionRetry ' If failure abort.
test SPILongIn, OCRCheckMask wz ' If voltage not supported abort.
shl SPILongIn, #1 wc '
if_z_or_nc jmp #instructionRetry '
shl SPILongIn, #1 wc ' SDHC supported or not.
if_c mov SPIShift, #0 '
if_nc mov SPIShift, #9 '
' //////////////////////Set Block Length///////////////////////////////////////////////////////////////////////////////////////
mov SPICommandOut, #($40 | 16) ' Send out command 16.
mov SPIParameterOut, fiveHundredAndTwelve '
call #commandSPI '
call #shutdownSPI '
tjnz SPIResponceIn, #instructionRetry ' If failure abort.
' //////////////////////Read CSD Register//////////////////////////////////////////////////////////////////////////////////////
mov SPICommandOut, #($40 | 9) ' Ask the card for its CSD register.
mov SPIParameterOut, #0 '
call #commandSPI '
tjnz SPIResponceIn, #instructionRetry ' If failure abort.
call #repsonceSPI '
cmp SPIResponceIn, #$FE wz '
if_nz jmp #instructionRetry '
mov counter, #16 ' Setup to read the CSD register.
movd readCSDModify, #cardSpecificData '
readCSDLoop call #readSPI ' Read the CSD register in.
readCSDModify mov 0, SPIDataIn '
add readCSDModify, fiveHundredAndTwelve '
djnz counter, #readCSDLoop '
call #wordSPI ' Shutdown SPI clock.
call #shutdownSPI '
' //////////////////////Read CID Register//////////////////////////////////////////////////////////////////////////////////////
mov SPICommandOut, #($40 | 10) ' Ask the card for its CID register.
mov SPIParameterOut, #0 '
call #commandSPI '
tjnz SPIResponceIn, #instructionRetry ' If failure abort.
call #repsonceSPI '
cmp SPIResponceIn, #$FE wz '
if_nz jmp #instructionRetry '
mov counter, #16 ' Setup to read the CID register.
mov buffer, CUIAddress '
readCIDLoop call #readSPI ' Read the CID register in.
wrbyte SPIDataIn, buffer '
add buffer, #1 '
djnz counter, #readCIDLoop '
wrbyte fiveHundredAndTwelve, buffer ' Clear the last byte for string compare.
call #wordSPI ' Shutdown SPI clock.
call #shutdownSPI '
' //////////////////////Setup Card Variables///////////////////////////////////////////////////////////////////////////////////
neg SPITiming, #1 ' Setup SPI parameters.
testn cardType, #0 wz, wc ' Determine CSD structure version.
if_nz test cardSpecificData, #$40 wc '
if_nz test cardSpecificData, #$80 wz '
if_nc_and_z mov counter, (cardSpecificData + 6) ' Extract card size.
if_nc_and_z and counter, #$3 '
if_nc_and_z shl counter, #10 '
if_nc_and_z mov buffer, (cardSpecificData + 7) '
if_nc_and_z shl buffer, #2 '
if_nc_and_z mov cardSize, (cardSpecificData + 8) '
if_nc_and_z shr cardSize, #6 '
if_nc_and_z or cardSize, counter '
if_nc_and_z or cardSize, buffer '
if_c_and_z mov counter, (cardSpecificData + 7) ' Extract card size.
if_c_and_z and counter, #$3F '
if_c_and_z shl counter, #16 '
if_c_and_z mov buffer, (cardSpecificData + 8) '
if_c_and_z shl buffer, #8 '
if_c_and_z mov cardSize, (cardSpecificData + 9) '
if_c_and_z or cardSize, counter '
if_c_and_z or cardSize, buffer '
if_nc_and_z mov buffer, (cardSpecificData + 9) ' Extract card size multiplier.
if_nc_and_z and buffer, #$3 '
if_nc_and_z shl buffer, #1 '
if_nc_and_z mov cardSizeMultiplier, (cardSpecificData + 10) '
if_nc_and_z shr cardSizeMultiplier, #7 '
if_nc_and_z or cardSizeMultiplier, buffer '
if_nc_and_z mov cardReadBlockLength, (cardSpecificData + 5) ' Extract read block length.
if_nc_and_z and cardReadBlockLength, #$F '
if_nc_and_z sub cardReadBlockLength, #9 ' Compute card sector count for 1.0 CSD.
if_nc_and_z add cardSizeMultiplier, #2 '
if_z add cardSize, #1 '
if_nc_and_z shl cardSize, cardReadBlockLength '
if_nc_and_z shl cardSize, cardSizeMultiplier '
if_c_and_z shl cardSize, #10 ' Compute card sector count for 2.0 CSD.
max cardSize, maxPositiveInteger ' Limit maximum partition size.
if_nz neg cardSize, #1 ' Unknown CSD structure. Card size to -1.
wrlong cardSize, CSCAddress ' Update Card Size.
mov cardSizeMinusOne, cardSize ' Compute maximum addressable sector.
sub cardSizeMinusOne, #1 '
neg cardMounted, #1 ' Return.
jmp #instructionLoop '
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' Read Block
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
readBlock rdlong SPIParameterOut, CSAAddress ' Read a block.
max SPIParameterOut, cardSizeMinusOne '
shl SPIParameterOut, SPIShift '
mov SPICommandOut, #($40 | 17) '
call #commandSPI '
tjnz SPIResponceIn, #instructionRetry ' If failure abort.
call #repsonceSPI '
cmp SPIResponceIn, #$FE wz '
if_nz jmp #instructionRetry '
mov counter, fiveHundredAndTwelve ' Setup loop.
readBlockModify rdlong buffer, CBAAddress '
readBlockLoop call #readSPI ' Read data into memory.
wrbyte SPIDataIn, buffer '
add buffer, #1 '
djnz counter, #readBlockLoop '
call #wordSPI ' Shutdown SPI clock.
call #shutdownSPI '
readBlock_ret jmp #instructionLoop ' Return. Becomes RET when rebooting.
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' Write Block
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
writeBlock rdlong SPIParameterOut, CSAAddress ' Write a block.
max SPIParameterOut, cardSizeMinusOne '
shl SPIParameterOut, SPIShift '
mov SPICommandOut, #($40 | 24) '
call #commandSPI '
tjnz SPIResponceIn, #instructionRetry ' If failure abort.
mov SPIDataOut, #$FE ' Send start of data token.
call #writeSPI '
mov counter, fiveHundredAndTwelve ' Setup loop.
rdlong buffer, CBAAddress '
writeBlockLoop rdbyte SPIDataOut, buffer ' Write data out from memory.
add buffer, #1 '
call #writeSPI '
djnz counter, #writeBlockLoop '
call #wordSPI ' Write out the 16 bit CRC.
call #repsonceSPI ' If failure abort.
and SPIDataIn, #$1F '
cmp SPIDataIn, #$5 wz '
if_nz jmp #instructionRetry '
call #cardBusy ' Shutdown SPI clock.
call #shutdownSPI '
jmp #instructionLoop ' Return.
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' Reboot Chip
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
rebootChip rdlong buffer, CBAAddress ' Check to make sure its a reboot.
tjnz buffer, #instructionError '
' //////////////////////Shutdown Cogs//////////////////////////////////////////////////////////////////////////////////////////
mov counter, #8 ' Setup cog stop loop.
cogid buffer '
rebootCogLoop sub counter, #1 ' Stop all cogs but this one.
cmp counter, buffer wz '
if_nz cogstop counter '
tjnz counter, #rebootCogLoop '
' //////////////////////Setup Memory///////////////////////////////////////////////////////////////////////////////////////////
mov counter, #64 ' Setup to grab all sector addresses.
rdlong buffer, CSAAddress '
rebootSectorLoadLoop rdlong cardRebootSectors, buffer ' Get all addresses of the 64 sectors.
add buffer, #4 '
add rebootSectorLoadLoop, fiveHundredAndTwelve '
djnz counter, #rebootSectorLoadLoop '
' //////////////////////Clear Memory///////////////////////////////////////////////////////////////////////////////////////////
mov counter, fiveHundredAndTwelve ' Clear all memory. Pointer at 0.
shl counter, #6 '
mov buffer, #0 '
rebootCodeClearLoop sub counter, #4 '
wrlong buffer, counter '
tjnz counter, #rebootCodeClearLoop '
' //////////////////////Fill Memory////////////////////////////////////////////////////////////////////////////////////////////
mov readBlock, #0 ' Fill these two commands with NOPs.
mov readBlockModify, #0 '
mov cardCounter, #64 ' Ready to fill all memory. Pointer at 0.
rebootCodeFillLoop mov SPIParameterOut, cardRebootSectors ' Reuse read block code. Finish if 0.
tjz SPIParameterOut, #rebootReady '
add rebootCodeFillLoop, #1 '
call #readBlock '
djnz cardCounter, #rebootCodeFillLoop '
' //////////////////////Boot Interpreter///////////////////////////////////////////////////////////////////////////////////////
rebootReady rdword buffer, #$A ' Setup the stack markers.
sub buffer, #4 '
wrlong rebootStackMarker, buffer '
sub buffer, #4 '
wrlong rebootStackMarker, buffer '
rdbyte buffer, #$4 ' Switch to new clock mode.
clkset buffer '
coginit rebootInterpreter ' Restart running new code.
cogid buffer ' Shutdown.
cogstop buffer '
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' Card Status
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
cardStatus mov SPICommandOut, #($40 | 13) ' Send out command 13.
mov SPIParameterOut, #0 '
call #commandSPI '
call #byteSPI '
call #shutdownSPI '
or SPIResponceIn, SPILongIn wz ' Unmount the card on failure.
muxz cardMounted, #1 '
cardStatus_ret ret ' Return.
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' Card Busy
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
cardBusy mov counter, writeTimeout ' Setup loop.
cardBusyLoop call #readSPI ' Wait until card is not busy.
cmp SPIDataIn, #0 wz '
if_z djnz counter, #cardBusyLoop '
tjz counter, #instructionRetry '
cardBusy_ret ret ' Return.
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' Command SPI
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
commandSPI andn outa, chipSelectPin ' Activate the SPI bus.
call #readSPI '
mov SPIDataOut, SPICommandOut ' Send out command.
call #writeSPI '
movs writeSPI, #32 ' Send out parameter.
mov SPIDataOut, SPIParameterOut '
call #writeSPI '
movs writeSPI, #8 '
commandSPICRC mov SPIDataOut, #0 ' Send out CRC token.
call #writeSPI '
call #repsonceSPI ' Read in responce.
commandSPI_ret ret ' Return.
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' Responce SPI
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
repsonceSPI mov SPIResponceIn, readTimeout ' Setup responce poll counter.
repsonceSPILoop call #readSPI ' Poll for responce.
cmp SPIDataIn, #$FF wz '
if_z djnz SPIResponceIn, #repsonceSPILoop '
mov SPIResponceIn, SPIDataIn ' Move responce into return value.
repsonceSPI_ret ret ' Return.
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' Long SPI
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
longSPI add readSPI, #16 ' Read in 32, 16, or 8 bits.
wordSPI add readSPI, #8 '
byteSPI call #readSPI '
movs readSPI, #8 '
mov SPILongIn, SPIDataIn ' Move long into return value.
byteSPI_ret ' Return.
wordSPI_ret '
longSPI_ret ret '
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' Shutdown SPI
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
shutdownSPI call #readSPI ' Shutdown the SPI bus.
or outa, chipSelectPin '
call #readSPI '
shutdownSPI_ret ret ' Return.
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' Read SPI
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
readSPI mov SPICounter, #8 ' Setup counter to read in 1 - 32 bits.
mov SPIDataIn, #0 wc '
readSPIAgain mov phsa, #0 ' Start clock low.
tjnz SPITiming, #readSPISpeed '
' //////////////////////Slow Reading///////////////////////////////////////////////////////////////////////////////////////////
movi frqa, #%0000_0000_1 ' Start the clock - read 1 .. 32 bits.
readSPILoop waitpne clockPin, clockPin ' Get bit.
rcl SPIDataIn, #1 '
waitpeq clockPin, clockPin '
test dataOutPin, ina wc '
djnz SPICounter, #readSPILoop ' Loop until done.
jmp #readSPIFinish '
' //////////////////////Fast Reading///////////////////////////////////////////////////////////////////////////////////////////
readSPISpeed movi frqa, #%0010_0000_0 ' Start the clock - read 8 bits.
test dataOutPin, ina wc ' Read in data.
rcl SPIDataIn, #1 '
test dataOutPin, ina wc '
rcl SPIDataIn, #1 '
test dataOutPin, ina wc '
rcl SPIDataIn, #1 '
test dataOutPin, ina wc '
rcl SPIDataIn, #1 '
test dataOutPin, ina wc '
rcl SPIDataIn, #1 '
test dataOutPin, ina wc '
rcl SPIDataIn, #1 '
test dataOutPin, ina wc '
rcl SPIDataIn, #1 '
test dataOutPin, ina wc '
' //////////////////////Finish Up//////////////////////////////////////////////////////////////////////////////////////////////
readSPIFinish mov frqa, #0 ' Stop the clock.
rcl SPIDataIn, #1 '
cmpsub SPICounter, #8 ' Read in any remaining bits.
tjnz SPICounter, #readSPIAgain '
readSPI_ret ret ' Return.
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' Write SPI
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
writeSPI mov SPICounter, #8 ' Setup counter to write out 1 - 32 bits.
ror SPIDataOut, SPICounter '
writeSPIAgain mov phsa, #0 ' Start clock low.
tjnz SPITiming, #writeSPISpeed '
' //////////////////////Slow Writing//////////////////////////////////////////////////////////////////////////////////////////
movi frqa, #%0000_0000_1 ' Start the clock - write 1 .. 32 bits.
writeSPILoop shl SPIDataOut, #1 wc ' Set bit.
waitpne clockPin, clockPin '
muxc outa, dataInPin '
waitpeq clockPin, clockPin '
djnz SPICounter, #writeSPILoop ' Loop until done.
jmp #writeSPIFinish '
' //////////////////////Fast Writing//////////////////////////////////////////////////////////////////////////////////////////
writeSPISpeed shl SPIDataOut, #1 wc ' Write out data.
muxc outa, dataInPin '
movi frqa, #%0010_0000_0 ' Start the clock - write 8 bits.
shl SPIDataOut, #1 wc ' Write out data.
muxc outa, dataInPin '
shl SPIDataOut, #1 wc '
muxc outa, dataInPin '
shl SPIDataOut, #1 wc '
muxc outa, dataInPin '
shl SPIDataOut, #1 wc '
muxc outa, dataInPin '
shl SPIDataOut, #1 wc '
muxc outa, dataInPin '
shl SPIDataOut, #1 wc '
muxc outa, dataInPin '
shl SPIDataOut, #1 wc '
muxc outa, dataInPin '
' //////////////////////Finish Up//////////////////////////////////////////////////////////////////////////////////////////////
writeSPIFinish mov frqa, #0 ' Stop the clock.
or outa, dataInPin '
cmpsub SPICounter, #8 ' Write out any remaining bits.
tjnz SPICounter, #writeSPIAgain '
writeSPI_ret ret ' Return.
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' Data
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
cardMounted long 0
' //////////////////////Constants//////////////////////////////////////////////////////////////////////////////////////////////
fiveHundredAndTwelve long $200 ' Constant 512.
maxPositiveInteger long $7FFFFFFF ' Constant 2,147,483,647.
OCRCheckMask long %00_000000_00110000_00000000_00000000 ' Parameter check mask for OCR bits.
HCSBitMask long %01_000000_00000000_00000000_00000000 ' Parameter bit mask for HCS bit.
rebootInterpreter long ($0001 << 18) | ($3C01 << 4) ' Spin interpreter text boot information.
rebootStackMarker long $FFF9FFFF ' Spin interpreter stack boot information.
' //////////////////////Configuration Settings/////////////////////////////////////////////////////////////////////////////////
readTimeout long 0 ' 100 millisecond timeout.
writeTimeout long 0 ' 250 millisecond timeout.
dataOutCounterSetup long 0 ' Data out control.
clockCounterSetup long 0 ' Clock control.
dataInCounterSetup long 0 ' Data in control.
' //////////////////////Pin Masks//////////////////////////////////////////////////////////////////////////////////////////////
dataOutPin long 0
clockPin long 0
dataInPin long 0
chipSelectPin long 0
' //////////////////////Addresses//////////////////////////////////////////////////////////////////////////////////////////////
CBAAddress long 0
CSAAddress long 0
CSCAddress long 0
CCFAddress long 0
CEFAddress long 0
CUIAddress long 0
' //////////////////////Run Time Variables/////////////////////////////////////////////////////////////////////////////////////
buffer res 1
counter res 1
' //////////////////////Card Variables/////////////////////////////////////////////////////////////////////////////////////////
cardBuffer res 1
cardCounter res 1
cardType res 1
cardSize res 1
cardSizeMultiplier res 1
cardSizeMinusOne res 1
cardReadBlockLength res 1
cardWriteBlockLength res 1
cardSpecificData res 16
cardRebootSectors res 64
' //////////////////////SPI Variables//////////////////////////////////////////////////////////////////////////////////////////
SPICommandOut res 1
SPIParameterOut res 1
SPIResponceIn res 1
SPILongIn res 1
SPIShift res 1
SPITiming res 1
SPIDataIn res 1
SPIDataOut res 1
SPIBuffer res 1
SPICounter res 1
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
fit 496
DAT
' //////////////////////Variable Array/////////////////////////////////////////////////////////////////////////////////////////
cardBlockAddress long 0 ' Address of the block in memory to read bytes from and write bytes to.
cardSectorAddress long 0 ' Address of the sector on the card to write bytes to and read bytes from.
cardSectorCount long 0 ' The secure digital card sector count.
cardCommandFlag byte 0 ' The secure digital card driver function command flag.
cardErrorFlag byte 0 ' The secure digital card driver function result flag.
cardUniqueID byte 0[17] ' The secure digital card identification number.
cardLockID byte 0 ' The secure digital card driver lock number.
cardCogID byte 0 ' The secure digital card driver cog number.
DAT
' //////////////////////String Array///////////////////////////////////////////////////////////////////////////////////////////
dot byte ". ", 0
dotdot byte ".. ", 0
FSCorrupted byte "File System Corrupted", 0
FSUnsupported byte "File System Unsupported", 0
fileOrDirectoryNotFound byte "File Or Directory Not Found", 0
fileNotFound byte "File Not Found", 0
directoryNotFound byte "Directory Not Found", 0
fileIsReadOnly byte "File Is Read Only", 0
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
{{
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ 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. │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
}}
{{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}}
'' =================================================================================================
''
'' File....... jm_i2c.spin
'' Purpose.... Basic I2C driver
'' Author..... Jon "JonnyMac" McPhalen (aka Jon Williams)
'' Copyright (c) 2009-2010 Jon McPhalen
'' -- several portions inspired by code from Mike Green
'' -- see below for terms of use
'' E-mail..... jon@jonmcphalen.com
'' Started.... 28 JUL 2009
'' Updated.... 05 AUG 2010
''
'' =================================================================================================
{{
Low/Mid-level I2C routines.
Assumes that SCL and SDA are fixed in a given application and can be set on
initialization of the object. Code does not drive SCL or SDA pins high; pull-ups
are required on both pins.
Use "x" routines for a device with that uses a 16-bit address (e.g., EEPROM)
}}
con
#0, ACK, NAK
#28, BOOT_SCL, BOOT_SDA ' Propeller I2C pins
var
long scl ' clock pin of i2c buss
long sda ' data pin of i2c buss
pub init(sclpin, sdapin) | okay
'' Define I2C SCL (clock) and SDA (data) pins
scl := sclpin
sda := sdapin
dira[scl] := 0 ' float buss pins
dira[sda] := 0
pub putbyte(id, addr, value) | ackbit
'' Write byte to device w/8-bit addressing
'' -- id : device slave id
'' -- addr : 8-bit address in the device
ackbit := putpage(id, addr, 1, @value)
return ackbit
pub putword(id, addr, value) | ackbit
'' Write word to device w/8-bit addressing
'' -- id : device slave id
'' -- addr : 8-bit address in the device
ackbit := putpage(id, addr, 2, @value)
return ackbit
pub putlong(id, addr, value) | ackbit
'' Write long to device w/8-bit addressing
'' -- id : device slave id
'' -- addr : 8-bit address in the device
ackbit := putpage(id, addr, 4, @value)
return ackbit
pub putpage(id, addr, n, src) | ackbit
'' Write n bytes from src to device w/8-bit addressing
'' -- id : device slave id
'' -- addr : 8-bit address in the device
'' -- n : bytes to write
'' -- src : pointer to source valuee
''
'' Be mindful of address/page size in device to prevent page wrap-around
wait(id)
write(addr) ' lsb of address
ackbit := ACK ' assume okay
repeat n
ackbit |= write(byte[src++]) ' write a byte
stop
return ackbit
pub putstr(id, addr, src) | ackbit, b
'' Write (arbitrary-length) string at src to device w/8-bit addressing
'' -- id : device slave id
'' -- addr : 8-bit address in the device
'' -- src : pointer to z-string
if (addr => 0)
ackbit := ACK ' assume okay
repeat
b := byte[src++] ' get byte from string
ackbit |= putbyte(id, addr++, b) ' write to card
if (b == 0) ' end of string?
quit ' yes, we're done
else
ackbit := NAK
return ackbit
pub getbyte(id, addr) | value
'' Read byte from device w/8-bit addressing
'' -- id : device slave id
'' -- addr : 8-bit address in the device
getpage(id, addr, 1, @value)
return value & $0000_00FF
pub getword(id, addr) | value
'' Read word from device w/8-bit addressing
'' -- id : device slave id
'' -- addr : 8-bit address in the device
getpage(id, addr, 2, @value)
return value & $0000_FFFF
pub getlong(id, addr) | value
'' Read long from device w/8-bit addressing
'' -- id : device slave id
'' -- addr : 8-bit address in the device
getpage(id, addr, 4, @value)
return value
pub getpage(id, addr, n, dest)
'' Read n bytes from device w/8-bit addressing; output to dest
'' -- id : device slave id (e.g., $A0 for EEPROM)
'' -- addr : 16-bit address in the device
'' -- n : bytes to read
'' -- dest : pointer to the destination
''
'' Be mindful of address/page size in device to prevent page wrap-around
wait(id)
write(addr) ' lsb of address
start ' restart for read
write(id | $01) ' device read
repeat (n - 1)
byte[dest++] := read(ACK)
byte[dest] := read(NAK) ' last byte gets NAK
stop
pub getstr(id, addr, dest) | b
'' Read (arbitrary-length) z-string, store at dest
'' -- id : device slave id
'' -- addr : 8-bit address in the device
'' -- dest : destination address for string
if (addr => 0)
repeat
b := getbyte(id, addr++) ' read byte from card
byte[dest++] := b ' write to string
if (b == 0) ' at end?
quit ' if yes, we're done
pub putbytex(id, addr, value) | ackbit
'' Write byte to device w/16-bit addressing
'' -- id : device slave id
'' -- addr : 16-bit address in the device
ackbit := putpagex(id, addr, 1, @value)
return ackbit
pub putwordx(id, addr, value) | ackbit
'' Write word to device w/16-bit addressing
'' -- id : device slave id
'' -- addr : 16-bit address in the device
ackbit := putpagex(id, addr, 2, @value)
return ackbit
pub putlongx(id, addr, value) | ackbit
'' Write long to device w/16-bit addressing
'' -- id : device slave id
'' -- addr : 16-bit address in the device
ackbit := putpagex(id, addr, 4, @value)
return ackbit
pub putpagex(id, addr, n, src) | ackbit
'' Write n bytes from src to device w/16-bit addressing
'' -- id : device slave id
'' -- addr : 16-bit address in the device
'' -- n : bytes to write
'' -- src : pointer to source value
''
'' Be mindful of address/page size in device to prevent page wrap-around
wait(id)
write(addr.byte[1]) ' msb of address
write(addr.byte[0]) ' lsb of address
ackbit := ACK ' assume okay
repeat n
ackbit |= write(byte[src++]) ' write a byte
stop
return ackbit
pub putstrx(id, addr, src) | ackbit, b
'' Write string at src to device w/8-bit addressing
'' -- id : device slave id
'' -- addr : 16-bit address in the device
'' -- src : pointer to z-string
if (addr => 0)
ackbit := ACK ' assume okay
repeat
b := byte[src++] ' get byte from string
ackbit |= putbytex(id, addr++, b) ' write to card
if (b == 0) ' end of string?
quit ' yes, we're done
else
ackbit := NAK
return ackbit
pub getbytex(id, addr) | value
'' Read byte from device w/16-bit addressing
'' -- id : device slave id
'' -- addr : 16-bit address in the device
getpagex(id, addr, 1, @value)
return value & $0000_00FF
pub getwordx(id, addr) | value
'' Read word from device w/16-bit addressing
'' -- id : device slave id
'' -- addr : 8-bit address in the device
getpagex(id, addr, 2, @value)
return value & $0000_FFFF
pub getlongx(id, addr) | value
'' Read long from device w/16-bit addressing
'' -- id : device slave id
'' -- addr : 8-bit address in the device
getpagex(id, addr, 4, @value)
return value
pub getpagex(id, addr, n, dest)
'' Read n bytes from device w/16-bit addressing; output to dest
'' -- id : device slave id (e.g., $A0 for EEPROM)
'' -- addr : 16-bit address in the device
'' -- n : bytes to read
'' -- dest : pointer to the destination
''
'' Be mindful of address/page size in device to prevent page wrap-around
wait(id)
write(addr.byte[1]) ' msb of address
write(addr.byte[0]) ' lsb of address
start ' restart for read
write(id | $01) ' device read
repeat while (n > 1)
byte[dest++] := read(ACK)
--n
byte[dest] := read(NAK) ' last byte gets NAK
stop
pub getstrx(id, addr, dest) | b
'' Read (arbitrary-length) z-string, store at dest
'' -- id : device slave id
'' -- addr : 16-bit address in the device
'' -- dest : destination address for string
if (addr => 0)
repeat
b := getbytex(id, addr++) ' read byte from device
byte[dest++] := b ' write to string
if (b == 0) ' at end?
quit ' if yes, we're done
pub wait(id) | ackbit
'' Waits for I2C device to be ready for new command
repeat
start
ackbit := write(id & $FE)
until ackbit == ACK
pub start
'' Create I2C start sequence
'' -- will wait if I2C buss SDA pin is held low
dira[sda] := 0 ' float SDA (1)
dira[scl] := 0 ' float SCL (1)
repeat while (ina[scl] == 0) ' allow "clock stretching"
outa[sda] := 0
dira[sda] := 1 ' SDA low (0)
pub write(i2cbyte) | ackbit
'' Write byte to I2C buss
outa[scl] := 0
dira[scl] := 1 ' SCL low
i2cbyte <<= constant(32-8) ' move msb (bit7) to bit31
repeat 8 ' output eight bits
dira[sda] := ((i2cbyte <-= 1) ^ 1) ' send msb first
dira[scl] := 0 ' SCL high (float to p/u)
dira[scl] := 1 ' SCL low
dira[sda] := 0 ' relase SDA to read ack bit
dira[scl] := 0 ' SCL high (float to p/u)
ackbit := ina[sda] ' read ack bit
dira[scl] := 1 ' SCL low
return (ackbit & 1)
pub read(ackbit) | i2cbyte
'' Read byte from I2C buss
outa[scl] := 0 ' prep to write low
dira[sda] := 0 ' make input for read
repeat 8
dira[scl] := 0 ' SCL high (float to p/u)
i2cbyte := (i2cbyte << 1) | ina[sda] ' read the bit
dira[scl] := 1 ' SCL low
dira[sda] := !ackbit ' output ack bit
dira[scl] := 0 ' clock it
dira[scl] := 1
return (i2cbyte & $FF)
pub stop
'' Create I2C stop sequence
outa[sda] := 0
dira[sda] := 1 ' SDA low
dira[scl] := 0 ' float SCL
repeat while (ina[scl] == 0) ' hold for clock stretch
dira[sda] := 0 ' float SDA
dat
{{
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 NON-INFRINGEMENT. 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.
}}
{{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}}
Universal Ground Station
{{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}}
Universal Ground Station