''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Boat Computer V4 ' Geoff Graham, Apr 16 ' ' Requires MMBasic 5.1 or later and an ILI9341 based LCD panel with touch ' Plus a GPS module ' ' V1: Original version ' V2: Modified to allow up to 57 POIs, suppressed heading when stationary ' and improved rendering of the heading needle ' V3: Two fixes by Jim Hiley ' Prevent a crash caused by POI's that are a long distance away. ' Corrected erratic needle movement. This bug was introduced in V2. ' V4: Fixed an issue that prevented selecting the SET button for some POIs ' ' Modified Greg Hoyes for 4WD use ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Option autorun on Option explicit Option default integer ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Don't like the colour scheme? ' You can change all the colours here! ' colours used in the main screen Const cBGnd = RGB(0,0,205) ' background colour Const cBorder = RGB(yellow) ' border colour Const cSpeed = RGB(white) ' colour used for the speed Const cDetail = RGB(yellow) ' colour used for heading, poi, etc CONST cCompass = RGB(0,0,0) ' black ' colours used in menus Const cTitle = RGB(green) ' title colour Const cEntry = RGB(yellow) ' colour used for the entry Const cButton = RGB(cyan) ' the key colour Const cSave = RGB(white) ' the save button ' other colours used in keypad entry screens Const cDel = RGB(magenta) ' the delete button Const cSpecial = RGB(248, 184, 184) ' special buttons ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' the number of POIs allowed (the actual number is one more than this number) Const nbrpoi = 56 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' global arrays, argRMC$(20) is RMC data, argG$(20) is GGA data field Dim string arg$(20) Dim string argRMC$(20) DIM STRING argGGA$(20) DIM INTEGER lRMC = 0, lGGA = 0 Dim string baud(5) = ("", "4800", "9600", "14400", "19200", "38400") Dim Integer key_coord(17, 5) Dim String key_caption(17) ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' global variables ' variables used to track the main display Dim Integer keydwn, redraw = 1, DetailMode, OldMode = -1, LastDetails, SpeedMode, DemoMode ' variables involved with the time (these are saved in flash) Dim Integer TimeZone = 36000, C24Hour, DaylightSaving ' details of the POIs (these are saved in flash) Dim String POI.Title(nbrpoi) Length 10 Dim Integer POI.Lat(nbrpoi), POI.Lon(nbrpoi) ' data extracted from the GPS module Dim integer year, month, day, hour, min, sec, lat, lon, speed, heading = -1, oldheading = -2, old DIM INTEGER ZoneDayAdjust = 0, nMonths, nYears, nWeekDay DIM STRING cMonth, cDow, cSatellites, cAltitude ' misc global variables Dim integer i, j, LastDeg, GotTouchY = -1, GotTouchX = -1, NotYetMoved = 0 Dim string str DIM INTEGER nPoint DIM string cHeading DIM INTEGER xHeading ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' initialise the program and discover the GPS baud rate CLS ' initialise the POI names For i = 0 to nbrpoi : POI.Title(i) = "POI " + Str$(i + 1) : Next i ' demo mode is turned on by touching the center on power up If Touch(X) > 80 And Touch(X) < 240 And Touch(Y) > 60 And Touch(Y) < 180 Then DemoMode = 1 RBOX MM.HRes/2-140, MM.VRes/2-60, 280, 120, 10, RGB(yellow), RGB(blue) Text MM.HRes/2, MM.VRes/2 - 20, "Demo", CM, 2, 1, RGB(yellow), RGB(blue) Text MM.HRes/2, MM.VRes/2 + 20, "Mode", CM, 2, 1, RGB(yellow), RGB(blue) Do While Touch(X) <> -1 : Loop Else VAR Restore RBOX MM.HRes/2-140, MM.VRes/2-60, 280, 120, 10, RGB(white), RGB(blue) Text MM.HRes/2, MM.VRes/2 - 20, "Waiting", CM, 2, 1, RGB(white), RGB(blue) Text MM.HRes/2, MM.VRes/2 + 20, "for GPS", CM, 2, 1, RGB(white), RGB(blue) EndIf ' detect the GPS baud rate ' this is done by switching between baud rates until clear text is received If Not DemoMode Then Do For i = 1 To 5 WatchDog 4000 Open "COM1:" + baud(i) As #1 Pause 1000 If Instr(Input$(255, #1), "$GP") > 0 Then Exit Do Close #1 WatchDog 4000 Open "COM1:" + baud(i) + ",INV" As #1 Pause 1000 If Instr(Input$(255, #1), "$GP") > 0 Then Exit Do Close #1 Next i CLS RBOX MM.HRes/2-140, MM.VRes/2-60, 280, 120, 10, RGB(white), RGB(red) Text MM.HRes/2, MM.VRes/2 - 20, "GPS Module", CM, 2, 1, RGB(white), RGB(red) Text MM.HRes/2, MM.VRes/2 + 20, "Not Found", CM, 2, 1, RGB(white), RGB(red) Loop EndIf ' this interrupt is used to detect touch on the main screen SetPin Peek(Byte Peek(Word &H9D000090) + 23), INTL, TouchDown ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' this is the main program loop, it never exits Do Do WatchDog 5000 If DemoMode Then If Timer > 1000 Then argRMC$(0) = "GPRMC" lRMC = 1 lGGA = 1 'flags for GPS records found Timer = 0 EndIf Else Do While Input$(254, #1) <> "" : Loop 'clean out anything in the buffer lRMC = 0 lGGA = 0 'reset flags for GPS records found GetGPSRecord 'get a GPS record EndIf 'Option to change to mph or knots deleted 'If GotTouchY > 0 And GotTouchY < MM.VRes/2 Then ' ' if the top half of the screen was touched switch between kph/mph/knots ' GotTouchY = -1 ' SpeedMode = (SpeedMode + 1) Mod 2 'change to MOD 3 to enable knots ' If Not DemoMode Then VAR Save SpeedMode ' Pause 150 ' redraw = 1 'If GotTouchY > MM.VRes/2 and GotTouchX < MM.HRES/2 Then If GotTouchY > 0 and GotTouchX < MM.HRES/2 Then ' if the left half was touched move back one screen DetailMode = DetailMode - 1 IF DetailMode = -1 Then DetailMode = 2 ENDIF Pause 150 redraw = 1 GotTouchY = -1 GotTouchX = -1 ElseIf GotTouchY > 0 and GotTouchX > MM.HRES/2 Then ' if the right half was touched move forward one screen DetailMode = DetailMode + 1 IF DetailMode = 3 Then DetailMode = 0 ENDIF Pause 150 redraw = 1 GotTouchY = -1 GotTouchX = -1 EndIf Loop Until argRMC$(0) = "GPRMC" ' we want the RMC and GGA record If argRMC$(2) = "V" Then ' "V" means not locked on CLS RBOX MM.HRes/2-150, MM.VRes/2-60, 300, 120, 10, RGB(white), RGB(blue) Text MM.HRes/2, MM.VRes/2 - 20, "Looking for", CM, 2, 1, RGB(white), RGB(blue) Text MM.HRes/2, MM.VRes/2 + 20, "Satellites", CM, 2, 1, RGB(white), RGB(blue) redraw = 1 Continue do ' go around again and keep looking EndIf ' the GPS has the correct data ' first extract the elements of the data that we want from the GPS record If DemoMode Then hour = 12 min = 45 sec = 23 day = 15 month = 8 year = 17 lat = -1131262 lon = 4411721 speed = 70 heading = 345 cAltitude = " Alt 1350m " cSatellites = "Sat count 8" NotYetMoved = 1 Else hour = Val( Left$(argRMC$(1), 2)) ' extract the time min = Val( Mid$(argRMC$(1), 3, 2)) sec = Val( Mid$(argRMC$(1), 5, 2)) day = Val( Left$(argRMC$(9), 2)) ' extract the date year = Val(Right$(argRMC$(9), 2)) month = Val( Mid$(argRMC$(9), 3, 2)) lat = Val(Mid$(argRMC$(3), 1, 2)) * 36000 + Val(Mid$(argRMC$(3), 3, 7)) * 600 If argRMC$(4) = "S" Then lat = -lat lon = Val(Mid$(argRMC$(5), 1, 3)) * 36000 + Val(Mid$(argRMC$(5), 4, 7)) * 600 If argRMC$(6) = "W" Then lon = -lon speed = Val(argRMC$(7)) if speed < 2 then speed = 0 and NotYetMoved = 1 ' stop any dithering when stopped IF speed >= 2 THEN NotYetMoved = 1 if speed >= 2 AND NotYetMoved <> 0 then heading = Val(argRMC$(8)) ' leave at last value IF argGGA$(9) <> "" THEN cAltitude = " Alt " + argGGA$(9) + "m " ELSE cAltitude = " Alt unknown " ENDIF IF argGGA$(7) <> "" THEN cSatellites = "Sat count " + argGGA$(7) else cSatellites = "Sat count ?" endif EndIf xheading = Int((heading+11.25) / 22.5) Select Case xheading Case 0 : cHeading = " N " Case 1 : cHeading = "NNE" Case 2 : cHeading = " NE " Case 3 : cHeading = "ENE" Case 4 : cHeading = " E " Case 5 : cHeading = "ESE" Case 6 : cHeading = " SE " Case 7 : cHeading = "SSE" Case 8 : cHeading = " S " Case 9 : cHeading = "SSW" Case 10 : cHeading = " SW " Case 11 : cHeading = "WSW" Case 12 : cHeading = " W " Case 13 : cHeading = "WNW" Case 14 : cHeading = " NW " Case 15 : cHeading = "NNW" Case 16 : cHeading = " N " End Select IF hour + timezone/3600 > 24 Then ZoneDayAdjust = 1 day = day + 1 Endif Select Case day CASE 32 If month=1 OR month=3 OR month = 5 OR month = 7 OR month = 8 OR month = 10 Then month = month + 1 day = 1 endif if month = 12 month = 1 day = 1 year = year + 1 endif case 31 if month = 4 or month = 6 or month = 9 or month = 11 then month = month + 1 day = 1 endif case 30 month = month + 1 day = 1 case 29 month = month + 1 day = 1 end select cMonth = mid$("JanFebMarAprMayJunJulAugSepOctNovDec", month*3-2, 3) argRMC$(0) = "" ' show that we have the data 'calc day of week, base 1/1/17 = Sunday SELECT CASE Month CASE 1: nMonths = 0 CASE 2: nMonths = 31 CASE 3: nMonths = 31 + 28 CASE 4: nMonths = 31 + 28 + 31 CASE 5: nMonths = 31 + 28 + 31 + 30 CASE 6: nMonths = 31 + 28 + 31 + 30 + 31 CASE 7: nMonths = 31 + 28 + 31 + 30 + 31 + 30 CASE 8: nMonths = 31 + 28 + 31 + 30 + 31 + 30 + 31 CASE 9: nMonths = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 CASE 10: nMonths = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 CASE 11: nMonths = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 CASE 12: nMonths = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 END SELECT SELECT CASE year CASE 17: nYears = 0 CASE 18: nYears = 365 CASE 19: nYears = 365 + 365 CASE 20: nYears = 365 + 365 + 365 'leap year IF Month > 2 THEN nMonths = nMonths + 1 CASE 21: nYears = 365 + 365 + 365 + 366 CASE 22: nYears = 365 + 365 + 365 + 366 + 365 CASE 23: nYears = 365 + 365 + 365 + 366 + 365 + 365 CASE 24: nYears = 365 + 365 + 365 + 366 + 365 + 365 + 365'leap year IF Month > 2 THEN nMonths = nMonths + 1 CASE 25: nYears = 365 + 365 + 365 + 366 + 365 + 365 + 365 + 366 CASE 26: nYears = 365 + 365 + 365 + 366 + 365 + 365 + 365 + 366 + 365 CASE 27: nYears = 365 + 365 + 365 + 366 + 365 + 365 + 365 + 366 + 365 + 365 CASE 28: nYears = 365 + 365 + 365 + 366 + 365 + 365 + 365 + 366 + 365 + 365 + 365 'leap year IF Month > 2 THEN nMonths = nMonths + 1 END SELECT nWeekDay = (Day + nMonths + nYears) MOD 7 SELECT CASE nWeekDay CASE 0: cDow = "Saturday" CASE 1: cDow = "Sunday" CASE 2: cDow = "Monday" CASE 3: cDow = "Tuesday" CASE 4: cDow = "Wednesday" CASE 5: cDow = "Thursday" CASE 6: cDow = "Friday" END SELECT ' redraw the fixed details on the main screen If redraw = 1 Then Box 0, 0, MM.HRes-1, MM.VRes-1, 4, cBorder, cBGnd EndIf redraw = 0 ' update the speed Select Case SpeedMode Case 0 IF DetailMode = 0 THEN Text 210, 15, Str$(Cint(speed * 1.852), 3) ,RT , 3, 2, cBorder, cBGnd Text 230, 20, "kph",LT , 2, 1, cBorder, cBGnd ELSE Text 65, 25, Str$(Cint(speed * 1.852), 3), , 3, 1, cBorder, cBGnd Text 180, 30, "kph", , 2, 1, cBorder, cBGnd ENDIF Case 1 IF DetailMode = 0 THEN Text 210, 15, Str$(Cint(speed * 1.15078), 3),RT , 3, 2, cBorder, cBGnd Text 230, 20, "mph",LT , 2, 1, cBorder, cBGnd ELSE Text 65, 25, Str$(Cint(speed * 1.15078), 3), , 3, 1, cBorder, cBGnd Text 180, 30, "mph", , 2, 1, cBorder, cBGnd ENDIF End Select ' update the details panel (the lower half of the screen) Select Case DetailMode Case 0 ' display the speed and compass If heading >= 0 Then Text MM.HRes/2, 135, cHeading, C, 1, 3, cSpeed, cBGnd Endif IF (DetailMode <> OldMode AND NotYetMoved <> 0) OR (oldheading <> heading AND (NotYetMoved <> 0 or DemoMode)) THEN BOX 5, MM.VRes-58, MM.HRes-11, 54, 0, , cCompass Line 4, MM.VRes-62, MM.HRes-4, MM.VRes-62, 4, RGB(yellow) line MM.HRes/2, MM.VRes-58, MM.HRes/2, MM.VRes-5, 4, RGB(yellow) nPoint = heading - 50 + 60 'nPoint is integer, cannot be negative do while nPoint mod 10 <> 0 nPoint = nPoint + 1 loop do while nPoint < heading + 50 + 60 'draw lines at 10 deg intervals line 160-(heading-nPoint+60)*3, MM.VRes-58, 160-(heading-nPoint+60)*3, MM.VRes-5, 1, RGB(white) 'write cardinal points on compass dial if nPoint mod 30 = 0 then select case nPoint case 0, 360: TEXT 160-(heading-nPoint+60)*3, MM.VRes-40, "300", C,1,2,RGB(white), cCompass case 30, 390: TEXT 160-(heading-nPoint+60)*3, MM.VRes-40, "330", C,1,2,RGB(white), cCompass case 60, 420: TEXT 160-(heading-nPoint+60)*3, MM.VRes-50, "N", C,1,3,cSpecial, cCompass case 90, 450: TEXT 160-(heading-nPoint+60)*3, MM.VRes-40, "30", C,1,2,RGB(white), cCompass case 120, 480: TEXT 160-(heading-nPoint+60)*3, MM.VRes-40, "60", C,1,2,RGB(white), cCompass case 150: TEXT 160-(heading-nPoint+60)*3, MM.VRes-50, "E", C,1,3,cSpecial, cCompass case 180: TEXT 160-(heading-nPoint+60)*3, MM.VRes-40, "120", C,1,2,RGB(white), cCompass case 210: TEXT 160-(heading-nPoint+60)*3, MM.VRes-40, "150", C,1,2,RGB(white), cCompass case 240: TEXT 160-(heading-nPoint+60)*3, MM.VRes-50, "S", C,1,3,cSpecial, cCompass case 270: TEXT 160-(heading-nPoint+60)*3, MM.VRes-40, "210", C,1,2,RGB(white), cCompass case 300: TEXT 160-(heading-nPoint+60)*3, MM.VRes-40, "240", C,1,2,RGB(white), cCompass case 330: TEXT 160-(heading-nPoint+60)*3, MM.VRes-50, "W", C,1,3,cSpecial, cCompass end select ENDIF 'redraw box edge overwritten by nPoint IF 160-(heading-nPoint+60)*3 < 30 then line 0, MM.VRes-58, 0, MM.VRes-5, 5, cBorder if 160-(heading-nPoint+60)*3 > MM.HRES-30 then line MM.HRes-5, MM.VRes-58, MM.HRes-5, MM.VRes-5, 5, cBorder nPoint = nPoint + 10 loop ENDIF oldheading = heading Case 1 ' display the current coordinates CvtLat lat, str Text 290, 95, str, RT, 2, 1, cDetail, cBGnd CvtLon lon, str Text 290, 130, str, RT, 2, 1, cDetail, cBGnd Text MM.HRes/2, 185, cAltitude, CM, 2, 1, cDetail, cBGnd Text MM.HRes/2, 220, cSatellites, CM, 2, 1, cDetail, cBGnd Case 2 ' display the date and time text 160, 95, cDow, CT, 2, 1, cDetail, cBGnd str = str$(day,2) + " " + cmonth + " 20" + str$(year,2) text 40, 130, str$(day,2), , 2, 1, cDetail, cBGnd text 95, 130, cmonth, , 2, 1, cDetail, cBGnd text 180, 130, "20" + str$(year,2), , 2, 1, cDetail, cBGnd i = hour *3600 + min *60 + sec + TimeZone If DaylightSaving Then i = i + 3600 If i > 86400 Then i = i - 86400 If i < 0 Then i = i + 86400 If Not C24Hour Then j = i >= 12 * 3600 If j Then i = i - 12 * 3600 If i\3600 = 0 Then i = i + 12 * 3600 EndIf str = Str$(i\3600, 2) + ":" + Str$((i Mod 3600) \ 60, 2, 0, "0") + ":" + Str$(i Mod 60, 2, 0, "0") Text 4, 180, str, LT, 3, 1, cDetail, cBGnd If Not C24Hour Then If j Then Text 265, 204, "PM", LT, 2, 1, cDetail, cBGnd Else Text 265, 204, "AM", LT, 2, 1, cDetail, cBGnd EndIf EndIf End Select OldMode = DetailMode ' continue looping forever Loop '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' this is the touch interrupt used when the main screen is touched ' all it does is set a flag Sub TouchDown GotTouchY = Touch(y) GotTouchX = Touch(x) End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' subroutine to get a GPS record into the arrays argRMC$() and argGGA$() Sub GetGPSRecord Local integer i Local string x$ local integer rmc = 0 local integer gga = 0 Do while rmc + gga <> 2 do Do While Input$(1, #1) <> "$" If GotTouchY <> -1 Then Exit Sub ' exit if the screen has been touched Loop ' wait for the start For i = 0 To 20 arg$(i) = "" ' clear ready for data Do ' loops until a specific exit If GotTouchY <> -1 Then Exit Sub ' exit if the screen has been touched x$ = Input$(1, #1) ' get the character If x$ = "," Then Exit Do ' new data item, new field If x$ = "*" Then Exit do ' end of record, so return with it 'If x$ = "*" Then Exit Sub ' end of record, so return with it arg$(i) = arg$(i) + x$ ' add to the data Loop ' keep going If x$ = "*" Then Exit do ' end of record, so return with it Next i ' increment the field loop if arg$(0) = "GPRMC" then FOR i = 0 TO 20 argRMC$(i) = arg$(i) NEXT i rmc = 1 ENDIF if arg$(0) = "GPGGA" then FOR i = 0 TO 20 argGGA$(i) = arg$(i) NEXT i gga = 1 ENDIF Loop End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' routines to convert a coordinate from a string to an integer and vice versa ' integers are the coordinate in tens of seconds ' strings can be ddd.dddd or ddd mm.mmmmm or ddd mm ss.s format followed by ' N, S, E or W. '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' convert an integer latitude to a string Sub CvtLat lat As integer, lats As string Local Integer i i = Abs(lat) lats = str$(i/36000, 3, 5) If lat < 0 Then lats = lats + " S" Else lats = lats + " N" End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' convert an integer longitude to a string Sub CvtLon lon As integer, lons As string Local Integer i i = Abs(lon) lons = str$(i/36000, 3, 5) If lon < 0 Then lons = lons + " W" Else lons = lons + " E" End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' EOF BoatComputerV4GH8.bas