Since I wanted to use this device on a motorcycle, I devised something more compact: convert data coming from a GPS device into audio signal. The latter can then be fed to the 'audio in' connection of a video camera and be recorded as one of the two available audio channels (DV video cameras support 2 or 4 channels). That way all video recorded will have an equivalent synchronized GPS information on the audio channel.
I used a cheap serial (RS-232) Sirf-II GPS (Model:AQ-MAX aq-300, ebay price: about 15€) to supply NMEA data. I chose to generate simple DTMF pulses since it's a cheap, very easy and noise-proof way to transmit data. DTMF pulses are generated by an MT8880 tranceiver chip (only the transmitter part is used). Everything is been controled by a PICAXE 18X which also outputs GPS readings to an LCD screen.
Approx. dimensions: 12x8cm | |
A 185452 375865400 0234542470 05 00000 ### 300507 01839 B START UTCTIME LAT. LONGITUDE SATS SPEED HDG DATE ALTIT ENDThe four main data screens in the LCD resemble to the following:
---------------- Time: hh:mm:ss Date: DD/MM/YY ---------------- | ---------------- LT: 37o58.5376'N LG:023o47.1589'E ---------------- | ---------------- SPD: xx.13 Kts HDG: xxx Degrees ---------------- | ---------------- Alt: 0183.9m Sats Used: 05 ---------------- |
Picaxe chips are great: easy, versatile and cheap, they don't need a programmer, but have their limitations. It was tricky
to decode NMEA messages since they are variable length and picaxe couldn't read the entire line in memory or in its variables.
For example take a look at the following possible NMEA time/date GPRMC message parts:
Both sentences seem to be valid:
$GPRMC,164924.435,A,3758.5376,N,02347.1589,E,0.00,,230705,,,A*74 $GPRMC,164924.435,A,3758.5376,N,02347.1589,E,xx0.00,xxx.xx,230705,,,A*74Here are all possible UTC time/date-part positions with the equivalent label names in the code that handle them (date1-date8)
01234567890123 ------+------------+ ,,230705 x.xx,,230705,------- date1 xx.xx,,230705,------ date2 xxx.xx,,230705,----- date3 x.xx,x.xx,230705,--- date4 x.xx,xx.xx,230705,-- date5 xx.xx,x.xx,230705,-- date5 x.xx,xxx.xx,230705,- date6 xx.xx,xx.xx,230705,- date6 xxx.xx,x.xx,230705,- date6 xx.xx,xxx.xx,230705, date7 xxx.xx,xx.xx,230705, date7 xxx.xx,xxx.xx,230705 date8The following code copes (hopefuly) for all possible message length variations. The memory gets filled by data which are then fed to the DTMF chip.
'------------------------------------START-------------------------------------------------- 'Read gps NMEA data and convert to DTMF audio 'using a picaxe 18X and MT8880/CM8880 DTMF tranceiver 'http://www.picaxe.co.uk 'Can be used to record gps data to the audio channel of a video tape 'to synchronize video with gps information ' '(c) 2005 Spiros Ioannou -- sivann {at} softlab.ntua.gr 'Written by sivann 7/Aug/2005 'http://www.softlab.ece.ntua.gr/~sivann ' '18X firmware 5 ' firmware 8.5 'RAM gets filled like this: 'address bytes what example format '80-85 6 UTCTIME: 164924.435 hhmmss.sss (From GPRMC-1) '86-94 9 LATITUDE: 3758.5376,N ddmm.mmmm,N (or S)(From GPRMC-2) '95-104 10 LONGITUDE: 02347.1589,E dddmm.mmmm,E (or S)(From GPRMC-3) '105-106 2 Sats Used: 07 0-12 (From GPGGA-4) '107-111 5 speed: xx0.13 (Knots) (From GPRMC-5) '112-114 3 direction: xxx.-- (From GPRMC-5) '115-120 6 date: 230705 ddmmyy (From GPRMC-6) '121-125 5 altitude: x252.8 Meters (From GPGGA-4) '200 from 0 to 4, what to display in lcd ' pin connections: ' 7 6 5 4 3 2 1 0 '232out CS RW RS0 D3 D2 D1 D0 'NMEA example: 'fix: '$GPGGA,164924.435,3758.5376,N,02347.1589,E,1,07,1.1,252.8,M,,,,0000*02 '$GPRMC,164924.435,A,3758.5376,N,02347.1589,E,0.00,,230705,,,A*74 'nofix: '$GPGGA,235954.999,,,,,0,00,50.0,,M,,,,0000*2B '$GPRMC,235954.999,V,,,,,,,030404,,,N*4B ' '$--RMC,hhmmss.sss,A,ddmm.mmmm,N,dddmm.mmmm,E,x.xx,xxx.xx,ddmmyy,x.x,a*hh<CR><LF> ' 1) UTC Time (hhmmss.sss) ' 2) Status, A=data valid or V=data not valid ' 3) Latitude ddmm.mmmm ' 4) N or S ' 5) Longitude dddmm.mmmm ' 6) E or W ' 7) Speed over ground, knots ' 8) Course over ground (degrees) ' 9) Date, ddmmyy ' 10) Magnetic Variation, degrees ' 11) E or W ' 12) Checksum 'LCD CMDS: 'serout 7,N2400,(254,128) ' move to start of first line 'serout 7,N2400,(254,1) ' Clear Display (must be followed by a 'pause 30' command) '254,128 Move to line 1, position 1 '254, y Move to line 1, position x (where y = 128 + x) '254,192 Move to line 2, position 1 '254, y Move to line 2, position x (where y = 192 + x) '---------------------------------------------------------------------------------------------------------------------- 'SYMBOL RS_p = 4 ' Register-select pin (0=data). 'SYMBOL RW_p = 5 ' Read/Write pin (0=write). SYMBOL CS_p = 6 ' Chip-select pin (0=active). SYMBOL digit = b2 'digits to dial. 'SYMBOL SIN_p = 1 'pin connected to rs232 in (2) SYMBOL SIN_p = 7 'pin connected to rs232 in (2) SYMBOL SOUT_p = 7 'pin connected to rs232 out (3) or serial LCD SYMBOL SOUT_speed=N2400 pause 500 'for the lcd to initialize poke 200,0 gosub clearlcd serout SOUT_p,SOUT_speed,("GPS2DTMF v0.1") serout SOUT_p,SOUT_speed,(254,192) serout SOUT_p,SOUT_speed,("(C) sivann 2005") pause 2000 'init MT8880 'let pins = 255 ' All pins high to deselect MT8880. let pins = %01111111 let pins = %00011011 ' Set up CRA, next write to CRB. high CS_p let pins = %00010000 ' Clear register B; ready to send DTMF. high CS_p mainloop: high CS_p ' just to be sure deselect audio chip gosub clearlcd serout SOUT_p,SOUT_speed,("Waiting $GPRMC") 'debug pause 100 gosub gettimefix if b12 = "A" then validfix invalidfix: gosub clearlcd serout SOUT_p,SOUT_speed,("Invalid Sat. Fix") pause 1500 goto mainloop '--- Read data and store in RAM validfix: gosub clearlcd serout SOUT_p,SOUT_speed,("Fix VALID") serout SOUT_p,SOUT_speed,(254,192) 'goto line 2 serout SOUT_p,SOUT_speed,("Reading data...") 'store UTC TIME poke 80,b1 poke 81,b2 poke 82,b3 poke 83,b4 poke 84,b5 poke 85,b6 gosub getlat if b12="V" then invalidfix 'store LATITUDE poke 86,b1 poke 87,b2 poke 88,b3 poke 89,b4 poke 90,b5 poke 91,b6 poke 92,b7 poke 93,b8 poke 94,b9 gosub getlong if b12="V" then invalidfix 'store LONGITUDE poke 95,b1 poke 96,b2 poke 97,b3 poke 98,b4 poke 99,b5 poke 100,b6 poke 101,b7 poke 102,b8 poke 103,b9 poke 104,b10 gosub getsatalt 'must also read altitude on GGA poke 105,b1 poke 106,b2 if b3="," then hdop2 'hdop has two digits before comma if b4="." then h1alt1 if b5="." then h1alt2'hdop has one digit,alt has two before comma if b6="." then h1alt3 if b7="." then h1alt4 goto sataltinv 'how tall is everest again? hdop2: if b5="." then h2alt1 if b6="." then h2alt2 if b7="." then h2alt3 if b8="." then h2alt4 goto sataltinv 'invalid h1alt1: poke 121,"0" poke 122,"0" poke 123,"0" poke 124,b3 poke 125,b5 goto aftersatalt h1alt2: poke 121,"0" poke 122,"0" poke 123,b3 poke 124,b4 poke 125,b6 goto aftersatalt h1alt3: poke 121,"0" poke 122,b3 poke 123,b4 poke 124,b5 poke 125,b7 goto aftersatalt h1alt4: poke 121,b3 poke 122,b4 poke 123,b5 poke 124,b6 poke 125,b8 goto aftersatalt h2alt1: poke 121,"0" poke 122,"0" poke 123,"0" poke 124,b4 poke 125,b5 goto aftersatalt h2alt2: poke 121,"0" poke 122,"0" poke 123,b4 poke 124,b5 poke 125,b7 goto aftersatalt h2alt3: poke 121,"0" poke 122,b4 poke 123,b5 poke 124,b6 poke 125,b8 goto aftersatalt h2alt4: poke 121,b4 poke 122,b5 poke 123,b6 poke 124,b7 poke 125,b9 goto aftersatalt sataltinv: for b0=121 to 125 poke b0,"X" 'dateinv next b0 aftersatalt: gosub getspeed if b12="V" then invalidfix if b1="," then speedinv if b2="." then speed1 if b3="." then speed2 if b4="." then speed3 goto speedinv speed1: poke 107,"0" poke 108,"0" poke 109,b1 poke 110,b3 poke 111,b4 if b6="," then dirinv if b7="." then speed1dir1 if b8="." then speed1dir2 if b9="." then speed1dir3 goto dirinv speed1dir1: poke 112,"0" poke 113,"0" poke 114,b6 goto afterspeed speed1dir2: poke 112,"0" poke 113,b6 poke 114,b7 goto afterspeed speed1dir3: poke 112,b6 poke 113,b7 poke 114,b8 goto afterspeed speed2: poke 107,"0" poke 108,b1 poke 109,b2 poke 110,b4 poke 111,b5 if b7="," then dirinv if b8="." then speed2dir1 if b9="." then speed2dir2 if b10="." then speed2dir3 goto dirinv speed2dir1: poke 112,"0" poke 113,"0" poke 114,b7 goto afterspeed speed2dir2: poke 112,"0" poke 113,b7 poke 114,b8 goto afterspeed speed2dir3: poke 112,b7 poke 113,b8 poke 114,b9 goto afterspeed speed3: poke 107,b1 poke 108,b2 poke 109,b3 poke 110,b5 poke 111,b6 if b8="," then dirinv if b9="." then speed3dir1 if b10="." then speed3dir2 if b11="." then speed3dir3 goto dirinv speed3dir1: poke 112,"0" poke 113,"0" poke 114,b8 goto afterspeed speed3dir2: poke 112,"0" poke 113,b8 poke 114,b9 goto afterspeed speed3dir3: poke 112,b8 poke 113,b9 poke 114,b10 goto afterspeed speedinv: for b0=107 to 111 poke b0,"X" 'speed invalid or non-existent next b0 dirinv: 'if speed is invalid, direction isn't read poke 112,"X" ' direction invalid poke 113,"X" poke 114,"X" afterspeed: 'gosub clearlcd 'serout SOUT_p,SOUT_speed,("Waiting date") gosub getdate 'serout SOUT_p,SOUT_speed,("b0=",b0," b1=",b1," b2=",b2," b3=",b3," b4=",b4," b5=",b5," b6=",b6," b7=",b7," b8=",b8," b9=",b9," b10=",b10," b11=",b11," b12=",b12," b13=",b13,13,10) if b7=100 then date0 if b3 <> "." and b6="," then date1 if b4 <> "." and b7="," then date2 if b1 = "," then date3 if b3 = "," then date4 if b4 = "," then date5 if b5="," then date6 if b6="," and b3="." then date7 if b4="." then date8 goto dateinv date0: poke 115,b1 poke 116,b2 poke 117,b3 poke 118,b4 poke 119,b5 poke 120,b6 goto afterdate date1: poke 115,b0 poke 116,b1 poke 117,b2 poke 118,b3 poke 119,b4 poke 120,b5 goto afterdate date2: poke 115,b1 poke 116,b2 poke 117,b3 poke 118,b4 poke 119,b5 poke 120,b6 goto afterdate date3: poke 115,b2 poke 116,b3 poke 117,b4 poke 118,b5 poke 119,b6 poke 120,b7 goto afterdate date4: poke 115,b4 poke 116,b5 poke 117,b6 poke 118,b7 poke 119,b8 poke 120,b9 goto afterdate date5: poke 115,b5 poke 116,b6 poke 117,b7 poke 118,b8 poke 119,b9 poke 120,b10 goto afterdate date6: poke 115,b6 poke 116,b7 poke 117,b8 poke 118,b9 poke 119,b10 poke 120,b11 goto afterdate date7: poke 115,b7 poke 116,b8 poke 117,b9 poke 118,b10 poke 119,b11 poke 120,b12 goto afterdate date8: poke 115,b8 poke 116,b9 poke 117,b10 poke 118,b11 poke 119,b12 poke 120,b13 goto afterdate dateinv: for b0=115 to 120 poke b0,"X" 'dateinv next b0 afterdate: gosub clearlcd serout SOUT_p,SOUT_speed,("Writing data...") 'now all data is in RAM 'output DTMF 'start sequence b1="[" gosub dtout for b0 = 80 to 125 peek b0,b1 gosub dtout next b0 'stop sequence b1="]" gosub dtout 'update LCD here: LAT/LONG, SPD/HDG, HEIGHT, DATE gosub clearlcd peek 200,b13 branch b13, (lcd0,lcd1,lcd2,lcd3) lcd0: peek 80,b1 'Time, hhmmss peek 81,b2 peek 82,b3 peek 83,b4 peek 84,b5 peek 85,b6 serout SOUT_p,SOUT_speed,("Time: ",b1,b2,":",b3,b4,":",b5,b6) peek 115,b1 'Date, ddmmyy peek 116,b2 peek 117,b3 peek 118,b4 peek 119,b5 peek 120,b6 serout SOUT_p,SOUT_speed,(254,192) 'goto line 2 serout SOUT_p,SOUT_speed,("Date: ",b1,b2,"/",b3,b4,"/",b5,b6) goto afterlcd lcd1: '86-94 9 LATITUDE: 3758.5376,N '95-104 10 LONGITUDE: 02347.1589,E peek 86,b1 peek 87,b2 peek 88,b3 peek 89,b4 peek 90,b5 peek 91,b6 peek 92,b7 peek 93,b8 peek 94,b9 serout SOUT_p,SOUT_speed,("LT: ",b1,b2,223,b3,b4,".",b5,b6,b7,b8,"'",b9) peek 95,b1 peek 96,b2 peek 97,b3 peek 98,b4 peek 99,b5 peek 100,b6 peek 101,b7 peek 102,b8 peek 103,b9 peek 104,b10 serout SOUT_p,SOUT_speed,(254,192) 'goto line 2 serout SOUT_p,SOUT_speed,("LG:",b1,b2,b3,223,b4,b5,".",b6,b7,b8,b9,"'",b10) goto afterlcd lcd2: '107-111 5 speed: xx0.13 '112-114 3 direction: xxx.-- peek 107,b1 peek 108,b2 peek 109,b3 peek 110,b4 peek 111,b5 peek 112,b6 peek 113,b7 peek 114,b8 serout SOUT_p,SOUT_speed,("SPD: ",b1,b2,b3,".",b4,b5," Kts") serout SOUT_p,SOUT_speed,(254,192) serout SOUT_p,SOUT_speed,("HDG: ",b6,b7,b8,223) goto afterlcd: lcd3: '121-125 5 altitude: x252.8 peek 121,b1 peek 122,b2 peek 123,b3 peek 124,b4 peek 125,b5 serout SOUT_p,SOUT_speed,("Alt: ",b1,b2,b3,b4,".",b5,"m") serout SOUT_p,SOUT_speed,(254,192) '105-106 2 Sats Used: 07 0-12 peek 105,b1 peek 106,b2 serout SOUT_p,SOUT_speed,("Sats Used: ",b1,b2) goto afterlcd: afterlcd: ;rotate counter 'peek 200,b13 b13=b13+1 b13=b13%4 poke 200,b13 pause 6000 goto mainloop end '------ Read from Serial Port ------------------------------------------------------------------------------------------------- '-- gettimefix ------ gettimefix: 'b12 contains A/V serin SIN_p, N4800, ("$GPRMC,"), b1,b2,b3,b4,b5,b6, b0,b0,b0,b0,b0,b12 return '-- GETLAT -------- getlat: 'b9 contains N/S 'b12 contains A/V serin SIN_p,N4800, ("$GPRMC,"), b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0, b12, b0, b1,b2,b3,b4, b0,b5,b6,b7,b8,b0, b9 return '-- GETLONG -------- getlong: 'b10 contains E/S 'b12 contains A/V serin SIN_p,N4800,("$GPRMC,"),b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0, b12, b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0, b1,b2,b3,b4,b5, b0, b6,b7,b8,b9, b0, b10 return '--getsatalt ------- getsatalt: serin SIN_p,N4800,("$GPGGA,"),b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0, b1,b2, b0,b0,b0,b0,b0, b3,b4,b5,b6,b7,b8,b9 return '-- GETSPEED ------- getspeed: 'b12 contains A/V serin SIN_p,N4800,("$GPRMC,"),b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0, b12,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0, b1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11 return '--- GETDATE ------- getdate: peek 107,b0 if b0="X" then dt1 'speed invalid serin SIN_p,N4800,("$GPRMC,"),b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0, b0,b0,b0,b0,b0,b0,b0, b1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11,b12,b13 goto dtret dt1: b7=100 serin SIN_p,N4800,("$GPRMC,"),b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0, b0,b0,b1,b2,b3,b4,b5,b6 dtret: return '-- DTMFOUT -------------------------------- dtout: 'output appropriate dtmf based on value of b1 'D1234567890*#ABCD 'serout SOUT_p,SOUT_speed,("#b1=",#b1,"(",b1,")") if b1>=49 AND b1<=57 then L19 '1-9 if b1=48 then L0 '0 if b1=44 then LC ', if b1=46 then LP '. if b1=69 then LE 'E if b1=87 then LW 'W if b1=78 then LN 'N if b1=83 then LS 'S if b1=65 then LA 'A if b1=86 then LV 'V if b1=91 then LSTART '[ if b1=93 then LEND '] if b1=88 then LX 'X L19: b3=b1-48 'numbers. convert ascii char to dec and store to b3 pins=b3 goto dtcont L0: pins=10 '0 is after 9 goto dtcont LC: pins=15 'C goto dtcont LP: pins=11 '* goto dtcont LE: pins=10 'E=0 goto dtcont LW: pins=1 'W=1 goto dtcont LN: pins=10 'N=0 goto dtcont LS: pins=1 'S=1 goto dtcont LA: pins=10 'A=0 (data valid) goto dtcont LV: pins=1 'V=1 (data invalid) goto dtcont LEND: pins=14 'B ends sequence goto dtcont LSTART: pins=13 'A starts sequence goto dtcont LX: pins=12 '# invalid/non-existent field goto dtcont dtcont: high CS_p ' Done with write. pause 250 ' Wait to dial next digit. DTMF pause. return '-- DTMFOUT end -------------------------------- clearlcd: serout SOUT_p,SOUT_speed,(254,128) ' move to start of first line serout SOUT_p,SOUT_speed,(254,1) ' Clear Display (must be followed by a 'pause 30' command) pause 30 return '-------------------------------------END------------------------------------------------