The idea of a microcontroller based GPS is not new to me.
A time ago while I was in the mountains I started wondering about having a small and cheap GPS helping me find some predefined waypoints. At that time I was just a student and started designing a small GPS with Microchip 18F family, a Nokia 3310 Display and a serial GPS. Not so powerful, but it worked. When I bought the M5Stack, given also its shape, I decided to create a new version of that GPS, this time more powerful and with a better display.
FunctionalitiesScope of this GPS is
- Show your track, your direction and the distance traveled/remaining
- Help you to head to some predefined waypoint
- Store your position on SD Card
Every 10 seconds, your position is stored on memory and on disk.By comparing two consecutive positions, the GPS show the direction of the movement and on which direction we should turn to find our waypoint.Additional points can be marked and reloaded afterwards as waypoints, initial place or any relevant point.Waypoints can be grouped, renamed, deleted or moved between groups.
I used a set of 10x10 pixels icons for the waypoints, to visualize them in differently according to the type, like Point of Interest, Restaurants, Hotels and so on.I generated from https://xbm.jazzychad.net/
// Food, 10x10px
const unsigned char epd_bitmap_food [] PROGMEM = {
0x24, 0x00, 0x48, 0x00, 0xfe, 0x00, 0x82, 0x03, 0x82, 0x02, 0x82, 0x03,
0x44, 0x00, 0x38, 0x00, 0x83, 0x01, 0xfe, 0x00
};
// Home, 10x10px
const unsigned char epd_bitmap_home [] PROGMEM = {
0x30, 0x00, 0x78, 0x00, 0xcc, 0x00, 0x86, 0x01, 0x02, 0x01, 0x02, 0x01,
0x5a, 0x01, 0x5a, 0x01, 0x1a, 0x01, 0xfe, 0x01
};
// 'Point Of Interest, 10x10px
const unsigned char epd_bitmap_hotel [] PROGMEM = {
0x89, 0x00, 0xc9, 0x01, 0xef, 0x03, 0x29, 0x02, 0xa9, 0x02, 0x20, 0x02,
0xa0, 0x02, 0x20, 0x02, 0xa0, 0x02, 0xe0, 0x03
};
// 'Transport', 10x10px
const unsigned char epd_bitmap_transport [] PROGMEM = {
0x30, 0x00, 0xfc, 0x00, 0x84, 0x00, 0xfe, 0x01, 0x32, 0x01, 0x86, 0x01,
0x84, 0x00, 0xcc, 0x00, 0x78, 0x00, 0x30, 0x00
};
// 'Hotel, 10x10px
const unsigned char epd_bitmap_poi [] PROGMEM = {
0x78, 0x00, 0x84, 0x00, 0x32, 0x01, 0x4a, 0x01, 0x32, 0x01, 0x84, 0x00,
0x48, 0x00, 0x48, 0x00, 0x30, 0x00, 0x30, 0x00
};
// 'Office', 10x10px
const unsigned char epd_bitmap_office [] PROGMEM = {
0xc6, 0xfd, 0xc4, 0xff, 0x8e, 0xff, 0xd8, 0xfe, 0x70, 0xfc, 0x30, 0xfc,
0x78, 0xfc, 0xcc, 0xfd, 0x86, 0xfc, 0x83, 0xfd
};
// 'shoopping', 10x10px
const unsigned char epd_bitmap_shopping [] PROGMEM = {
0x01, 0xfc, 0xfe, 0xfd, 0x02, 0xfe, 0x02, 0xfe, 0x02, 0xfe, 0xfe, 0xfd,
0x01, 0xfc, 0x01, 0xfc, 0xfe, 0xfd, 0x84, 0xfc
};
// 'Petrol Pump', 10x10px
const unsigned char epd_bitmap_petrol [] PROGMEM = {
0x00, 0xfc, 0x38, 0xff, 0x44, 0xfd, 0x44, 0xfd, 0xd4, 0xfd, 0x54, 0xfd,
0x54, 0xfd, 0x44, 0xfd, 0x44, 0xfc, 0xfe, 0xfc
};
// 'Traffic Signal', 10x10px
const unsigned char epd_bitmap_traffic_signal [] PROGMEM = {
0xfc, 0xfc, 0x87, 0xff, 0xb6, 0xfd, 0xb4, 0xfc, 0x87, 0xff, 0xb6, 0xfd,
0xb4, 0xfc, 0x84, 0xfc, 0xb6, 0xfd, 0xfc, 0xfc
};
As Hardware, I'm using an M5Stack Faces unit, mounting the GameBoy Keyboard, in my opinion the best one for the scope from the set provided with M5Faces.
Connected to the M5 Bus there connected a GPS module on Uart 2
Until now, I created a very simple UI, with some fixed icons on the top right:
- Satellites locked: green when the GPS is able to calculate the position.
- Dynamic Compass: green when we have compass calculated
- Battery: status of our battery charge
While the other visualization change to show different information.
All data get stored on the SD Card as a SQLite 3 database in different tables. The reason of a database is to have a quick way to store lots of points (our tracks) and eventually retrieve later, and to store and retrieve lots of waypoints in a simple structured data. On my current DB I have stored more than 20000 points allocating around 1 MB inside the SD Card.Using a database, we can easily retrieve data by timestamp or position, like search the closest point or eventually generate a group of waypoints from a stored track.
When necessary, I deploy a web console (included in the repo) on the M5 to manipulate the data. Query are mentioned on the code
Note: load of previous track is just alpha and I used only for some tests.
The Code: GPS Nmea parsingI created a small and simple code to parse the data from NEO-6M GPS Module connected on serial 2.
The parser evaluates only the data I'm interested in:
- GPRMC: to get the UTC time
- GPGSV: to get information about satellites in view
- GPGGA: to get current position.
Other strings are not evaluated at the moment
if (GPSRaw.available()) {
int ch = GPSRaw.read();
char card;
str.concat(char(ch));
if (ch == 10 ) {
String sent = str.substring(1, 6);
if (sent == "GPRMC") {
The Code: Display refreshEvery second, after the last message is parsed, a refresh function is triggered to update the display according to the desired state.
void refresh() {
switch (page) {
case 0:
// Gps Stat
sat_circle();
sat_stats();
// print_pos_info(220, 130);
print_sat_info(220, 130);
print_top_bar();
print_time();
print_menu();
break;
case 1:
//track
// if ( track_pos >= 0) {
print_track();
print_wp();
//print_wp_single();
//print_marks();
// }
print_odometer();
print_zoom();
print_top_bar();
// print_pos_info(220, 130);
//print_sat_info(220, 130);
print_compass_mini();
print_menu();
break;
case 2:
// if( (mygps.fixtime.endsWith("0")) and ( mygps.fix == 1)) {
print_compass();
//}
print_speed();
print_odometer();
print_top_bar();
print_track_mini();
print_wp_mini();
print_menu();
break;
case 3:
print_speed();
print_odometer();
print_go_wp();
print_top_bar();
print_track_mini();
print_wp_mini();
print_menu();
//print_pos_info(220, 130);
break;
case 4:
//list_wp();
list_points();
print_menu();
break;
case 5:
print_position();
break;
case 6:
rename_wp();
print_menu();
break;
case 7:
delete_wp();
print_menu();
break;
case 8:
list_groups();
print_menu();
break;
default:
break;
}
}
Several views are available as full screen or popup.These views are listed below:
Satellite view:Show position of satellites, quality of signal for the first 12 satellite received and number of satellites in use to fix the position.
Compass:It shows a compass with the red arrow heading to North. It's necessary to walk straight in the last 10 seconds to have a good precision. I'm going to deprecate this view.
Direction of waypointSimilar to the Compass view, it adds a green arrow heading to the waypoint.Below the graph, display the name of waypointOn the left, from top:
- Distance traveled (Trip)
- Distance and direction of the waypoint
- Current speed
- Miniature of the grid with waypoint selected
Display a grid of the track, adding a new point every 10 seconds.Center is the current position but can be moved using the joystick on the keyboard.Zoom can be adapted using contextual menu
On the left is visualized a mini compass and zoom value, from 0 to 9.
void print_compass_mini() {
float dx, dy;
int x = 270;
int y = 115;
int size = 45;
int x1, x2, x3, x4, y1, y2, y3, y4;
int xh1, yh1, xh2, yh2, xh3, yh3, xh4, yh4;
int north;
north = - movement.azimuth;
M5.Lcd.fillRect(x - 49, y - 49, 100, 100 , BLACK);
M5.Lcd.fillCircle(x, y, size, WHITE);
M5.Lcd.drawCircle(x, y, int(size * 0.9), BLACK);
M5.Lcd.drawCircle(x, y, int(size * 0.5), BLACK);
dx = float(size) * sin(float(north / 57.2958));
dy = float(size) * cos(float(north / 57.2958));
// Calculate only one point, generating remaining by difference
x1 = x + dx / 9;
y1 = y - dy / 9;
x2 = x - dy / 9;
y2 = y - dx / 9;
x3 = x - dx / 9;
y3 = y + dy / 9;
x4 = x + dy / 9;
y4 = y + dx / 9;
xh1 = x + dx;
yh1 = y - dy;
xh2 = x - dy;
yh2 = y - dx;
xh3 = x - dx;
yh3 = y + dy;
xh4 = x + dy;
yh4 = y + dx;
M5.Lcd.drawLine(x, y, xh1, yh1, GREEN);
M5.Lcd.fillTriangle(x1, y1, x3, y3, xh2, yh2, 0xB5B6);
M5.Lcd.fillTriangle(x4, y4, x2, y2, xh3, yh3, 0xB5B6);
M5.Lcd.fillTriangle(x1, y1, x3, y3, xh4, yh4, 0xB5B6);
M5.Lcd.fillTriangle(x4, y4, x2, y2, xh1, yh1, RED);
M5.Lcd.fillCircle(x, y, 2, BLACK);
}
Waypoint SelectionShow and allow selecting a waypoint from the list.Using left/right from the keyboard, select a new group of waypoints, top/down highlight and select a different waypoint.On the left, we can see details of the waypoint selected
Pop UpOn this visualization we can Rename, Delete or move a waypoint into a new group. It is not yet possible to add or manipulate groups, still required to access the DB.
My Custom GPS moduleI wanted to have the GPS chip connected internally n the main bus on the m5stack and also I wanted to have the antenna on the top, to improve the signal strength.
I did the initial tests using a proto module from M5Stack, but the antenna was not stable enough, so I created the new shell and moved the proto PCB on it.
This shell is printed on a small Easytreed Nano using black PLA.
The GPS module is connected to 3.3V, GND and pin 16 (RX on Uart2)
Can be better ifBattery, battery, and did I say battery? Yes, if you are in the mountains, you may want to be able to come back. The standard M5Faces 600mah battery is not enough. During my tests, on the bottom of my M5Faces I attached 2 flat battery from old Nokia phones, this provides additional 2500 mah, giving autonomy for a couple of hours. I would like to print a new shell that include the base, room for a bigger battery and some slider to improve the user interface. Maybe I can use standard AA Batteries, so they can be replaced during the hiking.
Software RoadmapVersion 0.5
- Code cleanup
- Glossary and variable names review
- UI Cleanup
- Power management review (example, slow down CPU when display is off)
Version 1.0
- Provide better user experience
- Load maps from SD card as images
- Download maps
- Export tracks as gpx or other standard format
- First implementation of data transmission via LoRa/ESPNow/other
- Load icons from SD
- Automatic switch to next waypoint following a predefined track
Version 2.0
- Fully implemented data transmission / reception
- Head to teammates
Comments