{{ Spin Stamp - HAPB4R4.spin Earl Foster KE5HDN Propeller Voyage to Near Space This program is design to read a GPS GGA sentence, display coordinates to a terminal and a 4x20 LCD screen, record GGA sentences every 4 seconds to a file located on an USB drive, activate external signal lights, and take pictures. All 8 COGs are in use at all times during program execution. The debug portion of the program is small and can be commentted out if addition Cogs were required. Debug is only used for testing, however, it is not currently turned off during flight. It uses 2 primary objects: Simple_LCD, FullDuplexSerialPlus where FullDuplexSerialPlus requires the use of an additional COG. There are 3 instances of FullDuplexSerialPlus object running in parallel. Simple_LCD calls another object named Simple_Serial. The three objects used were written by Parallax. I have included part of another object called USBdrive into this program since it also used the FullDuplexSerialPlus object. A number of the objects procedures have been striped out. Paul Deffenbaugh wrote the original object. The LCD is used when not connected to a computer and during pre-flight and post-flight operations. It is turned off during flight to conserve battery life. The GPS and USB drive are active at all times. The USB drive records GPS-GGA data every 4 seconds. The external lights are active only when below 1000 meters. The camera takes a picture every 30 seconds This program only uses the RX pin of the GPS, however, FullDuplexSerialPlus looks for both TX and RX. To bypass unnecessary pin useage on the Spin Stamp I have set the TX pin to Pin 16 which does not exist. There has been no irregularities in program execution using this method. }} con _clkmode = xtal1 + pll8x 'Use pll8x with the Spin stamp _xinfreq = 10_000_000 'Use 10_000_000 with the Spin stamp LCD_TX = 0 'Pin assignments LED_1 = 1 LED_2 = 2 LED_3 = 3 LED_4 = 4 LED_5 = 5 GPS_RX = 6 GPS_TX = 16 Camera = 7 DBPin4 = 8 DBPin3 = 9 DBPin2 = 10 DBPin1 = 11 RTS = 12 RXD = 13 TXD = 14 CTS = 15 PC_TX = 30 PC_RX = 31 GPS_Baud = 4_800 'Baud rates LCD_Baud = 19_200 PC_Baud = 9_600 GPS_Mode = 3 'Modes Use 0 for GPS15, 3 for GPS18 LCD_Mode = 4 PC_Mode = 0 LF = 10 'Special Chars CR = 13 var byte holder[82], gga[82] 'GPS data byte temp, index, found 'GPS scratch variables byte i, k, locator 'LCD cursor position variables long altitude 'Current altitude byte satellites 'Number of satellites available byte j, comma, height 'Altitude scratch variables long stack[500] 'Stack space for COGs obj LCD: "Serial_Lcd" 'Display to LCD USB: "fullduplexserialplus" 'MemoryStick in UART mode DEBUG: "fullduplexserialplus" 'Debug to terminal screen GPS: "fullduplexserialplus" 'Read GPS pub main {{ This is the main procedure }} bytefill(@gga, 0, 82) 'Uses Cog 0 bytefill(@holder, 0, 82) 'Uses Cog 0 cognew(Init_LCD, @stack[0]) 'Uses Cog 1 for LCD Init_Debug 'Starts up Cog 2 for serial communtications to computer cognew(Init_GPS, @stack[50]) 'Uses Cog 3 & 4, 3 for GPS, Cog 4 for serial communications cognew(Init_USB, @stack[300]) 'Uses Cog 5 & 6, Cog 5 for USB, Cog 6 for serial communications dira[Camera]~~ 'Make Camera Pin output, uses Cog 0 cognew(Toggle_External_Lights, @stack[450]) 'Starts up Cog 7 repeat Take_Picture 'Uses Cog 0 waitcnt(clkfreq*15 + cnt) pub Init_LCD {{ Initialize LCD and stores custom characters. This is the splash screen that shows up during bootup. While not necessary it adds a little interest to the LCD display }} lcd.init(LCD_TX,LCD_Baud,LCD_Mode) lcd.backlight(true) lcd.cls lcd.cursor(0) lcd.custom(0,@Balloon) lcd.custom(1,@Payload) lcd.custom(2,@EarthPanel1) lcd.custom(3,@EarthPanel2) lcd.custom(4,@EarthPanel3) lcd.custom(5,@EarthPanel4) lcd.custom(6,@EarthPanel5) lcd.custom(7,@EarthPanel6) waitcnt(clkfreq + cnt) lcd.gotoxy(0,3) 'Load the earth i:=2 repeat lcd.putc(i++) while i<8 waitcnt(clkfreq + cnt) 'Balloon and Payload come out lcd.gotoxy(0,3) lcd.putc(0) 'Balloon drifts into screen waitcnt(clkfreq/5 + cnt) lcd.gotoxy(0,3) lcd.putc(2) 'Put back EarthPanel1 lcd.putc(0) waitcnt(clkfreq/5 + cnt) lcd.gotoxy(1,3) lcd.putc(3) 'Put back EarthPanel2 lcd.putc(0) waitcnt(clkfreq/5 + cnt) lcd.gotoxy(2,3) lcd.putc(1) 'Show Payload lcd.gotoxy(2,2) lcd.putc(0) 'Show Balloon waitcnt(clkfreq/5 + cnt) lcd.gotoxy(2,3) lcd.putc(4) 'Put back EarthPanel3 lcd.putc(1) 'Show Payload lcd.gotoxy(2,2) lcd.str(string(" ")) 'Clear character lcd.putc(0) 'Show Balloon waitcnt(clkfreq/5 + cnt) lcd.gotoxy(3,3) lcd.putc(5) 'Put back EarthPanel4 lcd.gotoxy(3,2) lcd.str(string(" ")) 'Clear Balloon lcd.putc(1) 'Show Payload lcd.gotoxy(4,1) lcd.putc(0) waitcnt(clkfreq/5 + cnt) lcd.gotoxy(4,2) lcd.str(string(" ")) 'Clear char lcd.putc(1) 'Show Payload lcd.gotoxy(4,1) lcd.str(string(" ")) 'Clear char lcd.putc(0) 'Show Balloon waitcnt(clkfreq/5 + cnt) lcd.gotoxy(11,0) lcd.str(string("HAPB-4")) lcd.gotoxy(10,1) lcd.str(string("Designed")) lcd.gotoxy(13,2) lcd.str(string("By")) lcd.gotoxy(9,3) lcd.str(string("Earl Foster")) waitcnt(clkfreq*2 + cnt) FindSatellites DisplayCoordinates pub FindSatellites {{ This procedure is primary to make the user aware that that a satellite search is in progress. Depending on gps conditions such as reacquisition, warm, cold, autolocate, and sky search the amount of time can vary from 2 seconds to 5 minutes for satellite acquisition. Once 3 satellites are located the data is valid. }} repeat i:=0 repeat while i<4 lcd.cls lcd.gotoxy(0,i) lcd.str(string(" Seeking Satellites")) waitcnt(clkfreq/2 + cnt) i++ while satellites < 3 lcd.cls lcd.str(string("Satellites Acquired")) waitcnt(clkfreq*2 + cnt) lcd.backlight(false) 'Save power by turning off LED backlight pub DisplayCoordinates {{ Setup LCD for displaying Latitude, Longitude, Number of Satellites and Altitude Stores number of satellites and altitude for other procedures to use. }} lcd.cls lcd.home repeat lcd.str(string(" HAPB-4 KE5HDN")) lcd.gotoxy(0,1) lcd.str(string("Lat: ")) lcd.gotoxy(0,2) lcd.str(string("Long: ")) lcd.gotoxy(0,3) lcd.str(string("Sat:")) lcd.gotoxy(8,3) lcd.str(string("Alt:")) lcd.gotoxy(9,1) repeat i from 13 to 14 'Get the 1st 2 char for the degrees of Lat lcd.putc(gga[i]) lcd.str(string(" ")) 'Add a space repeat i from 15 to 21 'Get the minutes, seconds, and direction lcd.putc(gga[i]) lcd.putc(gga[23]) lcd.gotoxy(8,2) repeat i from 25 to 27 'Get the 1st 3 char for the degrees of Long lcd.putc(gga[i]) lcd.str(string(" ")) 'Add a space repeat i from 28 to 34 'Get the minutes, seconds, and direction lcd.putc(gga[i]) lcd.putc(gga[36]) lcd.gotoxy(4,3) repeat i from 40 to 41 'Display number of satellites lcd.putc(gga[i]) i := 0 'Reset counters to look for altitude locator := 0 lcd.gotoxy(12,3) repeat until locator == 9 'Start counting the commas in the string. k := gga[i] 'Altitude starts after if k == "," 'the 9th comma if you include the GPGGA which I do locator ++ i++ repeat while gga[i] <> "," 'Show decimaled altitude lcd.putc(gga[i++]) lcd.gotoxy(19,3) lcd.str(string("M")) 'Altitude is in meters pub Init_DEBUG {{ Starts up serial communication with a computer and displays information to a terminal screen }} debug.start(PC_RX, PC_TX, PC_Mode, PC_Baud) debug.str(string(CR,"Starting Program", CR)) debug.str(string(CR,"Running HAPB4R5",CR)) waitcnt(clkfreq + cnt) pub Init_GPS {{ Starts serial communication with GPS, calls up procedure for getting GGA string, and displays GGA string to the screen. A asterisk is displayed for all other gps strings. }} gps.start(GPS_RX, GPS_TX, GPS_Mode, GPS_Baud) satellites := altitude := 0 repeat get_gga_string debug.str(string(CR, "Receiving GGA String",13)) temp := 0 repeat until gga[temp] == "*" debug.tx(gga[temp++]) waitcnt(clkfreq + cnt) 'Pacing the display pub get_gga_string {{ Procedure captures each string transmitted by the GPS and looks for GGA sentence. Once GGA is found it stores the string for use by other procedures. }} bytefill(@holder, 0, 82) found := height := 0 repeat until found == 1 index := 0 repeat while gps.rx <> "$" 'look for the start of a sentence debug.tx(46) 'Displays a period, commented out during normal repeat while index < 82 'operations capture up to 82 characters for GGA string holder[index++] := gps.rx if holder[2] == "G" 'compare characters if holder[3] == "G" 'to see if this is if holder[4] == "A" 'the GGA sentence index := 0 repeat until holder[index] == CR 'Store only the GGA sentence gga[index] := holder[index] index++ found := 1 repeat index from 40 to 41 'Satellite position in GGA sentence satellites := satellites * 10 + (gga[index] - "0") 'Store the satellite value index := 0 comma := 0 repeat until comma == 9 'Start counting the commas in the string. j := gga[index] 'Altitude starts after the 9th comma if j == "," 'if you include the GPGGA which I do comma ++ index++ repeat while gga[index] <> "." 'Store only the integer value height := height * 10 + (gga[index++] - "0") altitude := height pub Take_Picture {{ Electronically triggers the camera }} !outa[Camera] 'Press the shuttle button waitcnt(clkfreq*2 + cnt) 'Hold it for 2 seconds. Time required to focus and shoot !outa[Camera] 'Release the shuttle button pub Init_USB {{ This procedure creates a single file called HAPB.TXT that will store the complete GGA sentence 15 times a minute. Each record is stored on a new line. }} Start_Drive(TXD,RTS,RXD,CTS) repeat waitcnt(clkfreq/4 + cnt) 'Wait time between operations OpenFile(@filename) waitcnt(clkfreq/4 + cnt) WriteLine(@gga) waitcnt(clkfreq/2 + cnt) CloseFile(@filename) waitcnt(clkfreq*3 + cnt) 'Total wait time between recordings is 4 sec PUB Start_Drive(tx_USB,rts_USB,rx_USB,cts_USB) | ioByte, started {{ The remaining code except for the DAT section was taken from another object written by Paul Deffenbaugh. Since his object used fulldepluxserialplus.spin I decided to incorporate a portion of that object directly into my code thereby saving on code space. }} if USB.start(rx_USB, tx_USB, 0, 9600) 'TX/RX, pins, true mode dira[rts_USB]~~ '~~ Set I/O pin to output direction outa[rts_USB] :=0 'LOW RTS Take Vinculum Out Of Reset getSerialBytes USB.rxflush repeat USB.str(string("E",CR)) 'SEROUT TX\CTS, Baud, ["E", CR], Sync Command Character ioByte := USB.rxtime(1000) 'GOSUB Get_Data ' Get Response until (ioByte == "E" OR ioByte == -1) if ioByte == "E" USB.rxflush repeat USB.str(string("e",CR)) 'SEROUT TX\CTS, Baud, ["e", CR], Sync Command Character ioByte := USB.rxtime(1000) 'GOSUB Get_Data ' Get Response until (ioByte == "e" OR ioByte == -1) waitcnt(clkfreq/10 + cnt) '100ms if ioByte == "e" USB.rxflush USB.tx(CR) 'Send CR to see if drive is present ioByte := USB.rxtime(1000) if ioByte == "D" started := 0 'SUCCESS (0=false) elseif ioByte == "N" started := -6 'LCD.debug(string("No Disk")) else started := -5 'LCD.debug(string("USB Failed: cr")) else started := -4 'LCD.debug(string("USB Failed: e")) else started := -3 'LCD.debug(string("USB Failed: E" )) else started := -1 'LCD.debug(string("USB Failed: Cog")) return started PUB OpenFile(NameofFile) {{ Open file for output (write) }} USB.rxflush USB.str(string("OPW ")) 'SEROUT TX\CTS, Baud, ["OPW seedfile.txt", CR] USB.str(NameofFile) USB.tx(CR) return WaitForCR PUB CloseFile(NameofFile) {{ Close file }} USB.rxflush USB.str(string("CLF ")) USB.str(NameofFile) USB.tx(CR) return WaitForCR PUB WriteLine(fileData) {{ Write data to file (file must be preopened) followed by CRLF }} USB.rxflush USB.str(string("WRF ")) '["WRF ", $00, $00, $00, $05, CR, DEC5 result, CR] USB.tx($00) USB.tx($00) USB.tx($00) USB.tx(strsize(fileData)+2) USB.tx(CR) USB.str(fileData) USB.tx(CR) USB.tx(LF) USB.tx(CR) return WaitForCR pub Toggle_External_Lights {{ FAA Title 14 Part 101regulations require external signal lighting that is visible for 5 miles and have a flash cycle time of 40 to 100 pulses per minute below 60,000 feet unless flying exempt. Because I am flying exempt the procedure is not needed but I included it for future use. I have setup the cycle time to meet regulation but and I am only using high output LEDS which can not be seen at distances greater then several hundred feet. It is used to help locate the payload in the dark. Altitude from the GPS is in meters. }} dira[1..5]~~ 'Make Pins 1 thru 5 outputs repeat 'Timing to set to have the external lights if altitude < 18288 'cycle 40 times per minute !outa[LED_1] waitcnt(clkfreq/4 + cnt) !outa[LED_1] !outa[LED_2] waitcnt(clkfreq/4 + cnt) !outa[LED_2] !outa[LED_3] waitcnt(clkfreq/4 + cnt) !outa[LED_3] !outa[LED_4] waitcnt(clkfreq/4 + cnt) !outa[LED_4] !outa[LED_5] waitcnt(clkfreq/4 + cnt) !outa[LED_5] PRI GetSerialBytes | ioByte repeat until ioByte == -1 ioByte := USB.rxtime(3000) PRI WaitForCR | ioByte repeat ioByte := USB.rxtime(3000) until ioByte == CR OR ioByte == -1 if ioByte == CR return True else return False dat {{ Custom HAPB display characters and filename declaration }} Balloon byte $04,$0E,$1F,$1F,$1F,$0E,$0E,$04 Payload byte $04,$04,$0E,$04,$1F,$1F,$00,$00 EarthPanel1 byte $00,$00,$00,$00,$01,$03,$07,$09 EarthPanel2 byte $00,$03,$07,$0F,$1F,$1F,$09,$08 EarthPanel3 byte $0F,$18,$10,$10,$10,$00,$00,$10 EarthPanel4 byte $1C,$03,$03,$00,$00,$01,$03,$07 EarthPanel5 byte $00,$18,$06,$01,$00,$10,$1C,$1E EarthPanel6 byte $00,$00,$00,$00,$10,$18,$04,$0E filename byte "GGAData.txt",0