''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




Waiting for input.



{{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}} Universal Ground Station



Waiting for input.