In my latest adventure I am taking on a charity walk which I devised for myself. I am walking the entire length of the Suffolk Coastline which is approx 100km.

http://fb.me/malwalks

The walk itself looks a bit like this To support my walk I have OS Maps using Viewranger for Android, the Ordnance Survey App itself and some paper maps, but as an absolute backup I took on a small electronics project to repurpose a development board into a GPS.

Objectives

#1 Display the British National Grid reference from GPS location

#2 Display my location on a map image of some sort

Board selection

I started with what I had lying around, it needed a UART, SD card slot and a screen. So I found my EMF Camp Badge (MK3) from 2016. SD Card Slot on the back

UART pins exposed across the top

320 x 240 colour display

Boot from SD card

I copied across the main scripts from the Mk3 Badge master zip file and ensured it was loading the boot and main scripts from the card, this was very easy.

WGS84 to British National Grid

Firstly I decided I should sort out converting the decimal WGS84 to BNG. I found some C code on a forum which I tested on an Arduino, then converted it to python and modified it to produce the BNG in the format i.e “TM123456”

``` #definitions for geometric conversions deg2rad = 0.017453292519943295 #(PI / 180) rad2deg = 57.29577951308232087 #(180/ PI) a = 6377563.396 # OSGB semi-major axis bb = 6356256.91 # OSGB semi-minor axis e0 = 400000 # OSGB easting of false origin n0 = -100000 # OSGB northing of false origin f0 = 0.9996012717 # OSGB scale factor on central meridian e2 = 0.0066705397616 # OSGB eccentricity squared lam0 = -0.034906585039886591 # OSGB false east phi0 = 0.85521133347722145 # OSGB false north af0 = 6375020.48098897069 #(a * f0) bf0 = 6353722.49048791244 #(b * f0) n = 0.0016732202503250876 #(af0 - bf0) / (af0 + bf0) WGS84_AXIS = 6378137 # a WGS84_ECCENTRIC = 0.00669438037928458 #e OSGB_AXIS = 6377563.396 #a2 OSGB_ECCENTRIC = 0.0066705397616 #e2 _xp = -446.448 #OSGB/Airy datums/parameters _yp = 125.157 _zp = -542.06 xrot = -0.000000728190149026 #_xr -0.1502 (_xr / 3600) * deg2rad yrot = -0.000001197489792340 #_yr -0.247 (_yr / 3600) * deg2rad zrot = -0.000004082616008623 #_zr -0.8421 (_zr / 3600) * deg2rad _sf = 0.0000204894 # s=20.4894 ppm LETTERS = [ [ "V", "W", "X", "Y", "Z" ], [ "Q", "R", "S", "T", "U" ], [ "L", "M", "N", "O", "P" ], [ "F", "G", "H", "J", "K" ], [ "A", "B", "C", "D", "E" ] ] ```

``` def right(s, amount): return s[-amount:] def left(s, amount): return s[:amount] ```
``` def Marc(phi): return bf0 * (((1 + n + ((5 / 4) * (n * n)) + ((5 / 4) * (n * n * n))) * (phi - phi0)) - (((3 * n) + (3 * (n * n)) + ((21 / 8) * (n * n * n))) * (sin(phi - phi0)) * (cos(phi + phi0))) + ((((15 / 8) * (n * n)) + ((15 / 8) * (n * n * n))) * (sin(2 * (phi - phi0))) * (cos(2 * (phi + phi0)))) - (((35 / 24) * (n * n * n)) * (sin(3 * (phi - phi0))) * (cos(3 * (phi + phi0))))) ```
``` def LLtoNE(latConv,lonConv,heightConv): latConv*= deg2rad # convert latitude to radians lonConv*= deg2rad # convert longitude to radians v = WGS84_AXIS / (sqrt(1 - (WGS84_ECCENTRIC *(sin(latConv) * sin(latConv))))) x = (v + heightConv) * cos(latConv) * cos(lonConv) y = (v + heightConv) * cos(latConv) * sin(lonConv) z = ((1 - WGS84_ECCENTRIC) * v + heightConv) * sin(latConv) # transform cartesian hx = x + (x * _sf) - (y * zrot) + (z * yrot) + _xp hy = (x * zrot) + y + (y * _sf) - (z * xrot) + _yp hz = (-1 * x * yrot) + (y * xrot) + z + (z * _sf) + _zp # Convert back to lat, lon lonConv = atan(hy / hx) p = sqrt((hx * hx) + (hy * hy)) latConv = atan(hz / (p * (1 - OSGB_ECCENTRIC))) v = OSGB_AXIS / (sqrt(1 - OSGB_ECCENTRIC * (sin(latConv) * sin(latConv)))) errvalue = 1.0 lat1 = 0 while errvalue > (1/1024): lat1 = atan((hz + OSGB_ECCENTRIC * v * sin(latConv)) / p) errvalue = abs(lat1 - latConv) latConv = lat1 output_airy_elevation = p / cos(latConv) - v # Convert OSGB36/Airy into OS grid eastings and northings # easting slat2 = sin(latConv) * sin(latConv) nu = af0 / (sqrt(1 - (e2 * (slat2)))) rho = (nu * (1 - e2)) / (1 - (e2 * slat2)) eta2 = (nu / rho) - 1 pp = lonConv - lam0 IV = nu * cos(latConv) clat3 = pow(cos(latConv), 3) tlat2 = tan(latConv) * tan(latConv) V = (nu / 6) * clat3 * ((nu / rho) - tlat2) clat5 = pow(cos(latConv), 5) tlat4 = pow(tan(latConv), 4) VI = (nu / 120) * clat5 * ((5 - (18 * tlat2)) + tlat4 + (14 * eta2) - (58 * tlat2 * eta2)) output_os_eastings = e0 + (pp * IV) + (pow(pp, 3) * V) + (pow(pp, 5) * VI) # northing M = Marc(latConv) I = M + (n0) II = (nu / 2) * sin(latConv) * cos(latConv) III = ((nu / 24) * sin(latConv) * pow(cos(latConv), 3)) * (5 - pow(tan(latConv), 2) + (9 * eta2)) IIIA = ((nu / 720) * sin(latConv) * clat5) * (61 - (58 * tlat2) + tlat4) output_os_northings = I + ((pp * pp) * II) + (pow(pp, 4) * III) + (pow(pp, 6) * IIIA) #letters xf = output_os_eastings/500000; yf = output_os_northings/500000; s1x = (trunc(xf)) + 2; s1y = (trunc(yf)) + 1; s1 = LETTERS[s1y][s1x]; xf = (output_os_eastings % 500000)/100000; yf = (output_os_northings % 500000)/100000; s2x = trunc(xf); s2y = trunc(yf); s2 = LETTERS[s2y][s2x]; output_grid = s1 + s2 output_east = right(str(trunc(output_os_eastings)), 5) output_north = right(str(trunc(output_os_northings)), 5) output = [output_os_eastings, output_os_northings, output_airy_elevation, output_grid, output_east, output_north,s1x,s1y,s2x,s2y] return output ```

Connect a GPS

The next step was to put a real GPS on it and get a location back. I selected a ublox Neo-6M and mounted it (shoddily) to the back of the Badge. It could have been much more compact, I won’t risk changing it now with 1 week to go to the start of the walk (28/09/20). I 3d printed an antenna mount and glued the antenna to it. It is connected with some header pins, so I could change it in a future revision. I wish all these boards layed out the UART or SPI or I2C pins in the space order and spacing, because it is a bit of a pain.

I tried the default large ceramic active antenna, a small ceramic active antenna, a small passive lcb antenna, and a wifi antenna (cut down to 1/8th wavelength) and found that while they all worked, this large ceramic antenna seemed to perform the best in real world test. Interfacing GPS

I found a python module online micropyGPS here https://github.com/inmcm/micropyGPS

I quickly ran out of RAM and had to take an axe to the module code and chop out stuff I wasn’t using (speed calculations & logging)

It was simple to interface the module, in the main loop it just reads characters from the UART and passes them into the module and prints them to the serial port (for testing the GPS)

``` my_gps = MicropyGPS() my_gps.coord_format = "dd" uart = UART(3, 9600, read_buf_len=1000) ```

``` while uart.any(): ----ledg.on() ----char = chr(uart.readchar()) ----my_gps.update(char) # Note the conversion to to chr, UART outputs ints normally ----output += char ----if (char == "\n"): --------print (output, end = '') --------output = "" ----ledg.off() ```

The location can be grabbed and converted

``` lat = float(my_gps.latitude) long = float(my_gps.longitude) location = LLtoNE(lat,long,0) ```

Interfacing the screen

Working with the screen is simple with ugfx, here are some examples I used

``` ugfx.area(0,0,140,20,ugfx.WHITE) ugfx.text(3, 0, str(location + "_" + location + "_" + location) + " ", ugfx.BLACK) ```

To meet objective #2, I loaded the SD cards with some gif files that represent each 1km square for example TM1941.gif, TM2041.gif etc and loaded up the current tile.

``` ugfx.display_image(0, 0, strimage) ugfx.display_image(240, 0, strimage_east) ```

strimage is the path to the current 1km tile, and strimage_east, is the tile to the east of that. this is because the tiles are 240×240 pixels, but the screen is 320 wide, so I get 80 pixels of the next tile displayed as well to use the screen real estate.

Final result

Once I put some logic around all the examples above to take input from the buttons and added code to draw the battery voltage and cursor. Here is a video demo.

These maps bear some similarity to the Ordnance Surveys maps, but they are of course not, these few maps tiles shown here were digitally recreated by myself using mspaint 😀

This site uses Akismet to reduce spam. Learn how your comment data is processed.