GPS Navigation with ODROID-GO

ODROID-GO is one of those small versatile extensible systems with which, given some imagination, you can create very useful and interesting projects. Here is one such project, that integrates a GPS sensor, to display GPS maps on an ODROID-GO.

Figure 1 - OSM Offline Map on Youtube

The source code can be obtained from git repo: https://github.com/ripper121/OdroidGoOSMGPSOffline

The needed firmware can be fetched from this location: https://goo.gl/PgYF6C

The key menu is as follows:

  • JoyPad = Move around
  • A/B = Zoom in/out (Zoom Level goes from 5-16, depending on which tile zoom levels you have)
  • Select / Start = Brightness
  • Menu = Reset Movement

Generate tiles

As expected, the design limitations of the ODROID-GO do not permit live maps. So a map would have to prefetched and stored for later use. Tiles are essentially these offline maps.

Tiles can be downloaded on MS Windows system using the TileDownloader.exe application from: https://bit.ly/2HDsLRh (EDITOR: google’s url shortener does not work for this link)

The single-line command (replace coordinates specific to your location) to create a tile and fetch it:

> TileDownloader.exe -URL \https://a.tile.openstreetmap.org/${z}/${x}/${y}.png 
-z 5 -left -0.489 -botton 51.28 -right 0.236 -top 51.686
Where the following options can be specified:

  • -URL : TileServer
  • -z : Zoom level

and the bounding box limits are:

  • -left : min-longitude
  • -bottom : min-latitude
  • -right : max-longitude
  • -top : max-latitude

In some regions, a comma ‘,’ may be the used instead of the period ‘.’, for the bounding box values.

Other tile servers at the following link can be used: https://wiki.openstreetmap.org/wiki/Tile_servers

Additional tiles can be obtained from: https://goo.gl/DRusXL

The offline maps can also be stored on an SDCard as seen here:

Figure 2 - ODROID-GO with offline OSM Map GPS Youtube video

It uses a Zoom Level from 5 to 14, with the Position marker as the red circle.

This "scroll around" is a manipulation of the GPS Coordinates via the JoyStick. Following are the steps to generate tiles (offline Map) for your area:

  • 1. Open Maperitive.exe (http://maperitive.net/)
  • 2. Move the map to your favorite position
  • 3. MAP->Set Geometry Bounds
  • 4. MAP->Set Printing Bounds

Now the area of the map you want to Export is set.

  • 5. TOOLS-> Generate Tiles (this can take some time, depending on the resolution of the Zoom Level)
  • 6. Now you will find some PNG files in the Maperitive /tiles folder
  • 8. Open Flexxi.exe (https://sourceforge.net/projects/flexxi-image-resizer/)
  • 9. Import the Tiles Folder
  • 10. Resize the Image to 240x240 px (best Fit for GO Screen)
  • 11. Convert to JPG Files
  • 12. Save
  • 13. Now you have your tiles in the correct size and format
  • 14. Copy the "TILES" (all Uppercase) folder to the root directory of your SD card

Open Arduino IDE and Flash the Code to your GO. This is the Arduino Code:

#include 

#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 240
#define TILE_SIZE 240

bool firstRun = true;
double zoom = 10;
double lat_rad = 50.8225313, lon_deg = 12.7508936;
double tileX = 0, tileY = 0;
double old_lat_rad = 0, old_lon_deg = 0;
double old_zoom = 0;
double old_tileY = 0, old_tileX = 0;
uint8_t brightness = 127;

void setup()
{
  // put your setup code here, to run once:
  Serial.begin(115200);
  GO.begin();
  GO.battery.setProtection(true);
  GO.lcd.clear();
  GO.lcd.setCursor(0, 0);

  if (!SD.begin()) {
    GO.lcd.println("Card Mount Failed");
    Serial.println("Card Mount Failed");
    return;
  }
  uint8_t cardType = SD.cardType();

  if (cardType == CARD_NONE) {
    GO.lcd.println("No SD card attached");
    Serial.println("No SD card attached");
    return;
  }

  Serial.print("SD Card Type: ");
  if (cardType == CARD_MMC) {
    GO.lcd.println("MMC");
    Serial.println("MMC");
  } else if (cardType == CARD_SD) {
    GO.lcd.println("SDSC");
    Serial.println("SDSC");
  } else if (cardType == CARD_SDHC) {
    GO.lcd.println("SDHC");
    Serial.println("SDHC");
  } else {
    GO.lcd.println("UNKNOWN");
    Serial.println("UNKNOWN");
  }

  uint64_t cardSize = SD.cardSize() / (1024 * 1024);
  Serial.printf("SD Card Size: %lluMB\n", cardSize);
  GO.lcd.printf("SD Card Size: %lluMB\n", cardSize);

  delay(3000);

  GO.lcd.clear();
  GO.lcd.setCursor(0, 0);
}

//setBrightness(uint8_t brightness),
void loop()
{
  GO.update();
  if (GO.JOY_X.isAxisPressed() == 1) {
    lon_deg += 0.0001;
    delay(10);
  }
  if (GO.JOY_X.isAxisPressed() == 2) {
    lon_deg -= 0.0001;
    delay(10);
  }
  if (GO.JOY_Y.isAxisPressed() == 2) {
    lat_rad += 0.0001;
    delay(10);
  }
  if (GO.JOY_Y.isAxisPressed() == 1) {
    lat_rad -= 0.0001;
    delay(10);
  }
  if (GO.BtnA.wasPressed() == 1) {
    zoom++;
  }
  if (GO.BtnB.wasPressed() == 1) {
    zoom--;
  }
  if (GO.BtnSelect.isPressed() == 1) {
    GO.lcd.setBrightness(brightness);
    brightness--;
  }
  if (GO.BtnStart.isPressed() == 1) {
    GO.lcd.setBrightness(brightness);
    brightness++;
  }

  if (lat_rad > 85.05112878)
    lat_rad = 85.05112878;
  if (lat_rad < -85.05112878) lat_rad = -85.05112878; if (lon_deg > 180)
    lon_deg = 180;
  if (lon_deg < -180) lon_deg = -180; if (zoom > 16)
    zoom = 16;
  if (zoom < 5) zoom = 5; if (brightness > 254)
    brightness = 254;
  if (brightness < 1)
    brightness = 1;

  //redraw only when something has changed
  if (old_lat_rad != lat_rad || old_lon_deg != lon_deg || old_zoom != zoom || firstRun) {
    double posX, posY, fractpart, intpart;
    //calculate from coordinates to tile numbers
    tileX = long2tilex(lon_deg, zoom);
    tileY = lat2tiley(lat_rad, zoom);
    //fractional part is the position of the your coordinats in the tile
    posX = modf(tileX , &intpart);
    posY = modf(tileY , &intpart);
    posX = (posX * TILE_SIZE) + (abs(DISPLAY_WIDTH - TILE_SIZE));
    posY = (posY * TILE_SIZE);

    //redraw only when something has changed
    if (uint16_t(old_tileX) != uint16_t(tileX) || uint16_t(old_tileY) != uint16_t(tileY)  || old_zoom != zoom  || firstRun) {
      String path = "/TILES/" + String(uint16_t(zoom)) + "/" + String(uint32_t(tileX)) + "/" + String(uint32_t(tileY)) + ".jpg";
      Serial.println(path);
      if (SD.exists(path)) {
        Serial.println("File found.");
        GO.lcd.clear();
        GO.lcd.setCursor(0, 0);
        //drawJpgFile(fs::FS &fs, const char *path, uint16_t x = 0, uint16_t y = 0, uint16_t maxWidth = 0, uint16_t maxHeight = 0, uint16_t offX = 0, uint16_t offY = 0, jpeg_div_t scale = JPEG_DIV_NONE),
        GO.lcd.drawJpgFile(SD, path.c_str(), (abs(DISPLAY_WIDTH - TILE_SIZE)));
      } else {
        GO.lcd.println("");
        GO.lcd.println("Debug:\nFile not found.");
        Serial.println("File not found.");
      }
      firstRun = false;
    }

    GO.lcd.drawRect(int32_t(posX), int32_t(posY), 4, 4, RED);
    GO.lcd.fillRect(0, 0, abs(DISPLAY_WIDTH - TILE_SIZE), DISPLAY_HEIGHT, BLACK);
    GO.lcd.setCursor(0, 0);
    GO.lcd.println("Battery:");
    GO.lcd.println(String(GO.battery.getPercentage()) + "%");
    GO.lcd.println("Lon_deg:");
    GO.lcd.println(String(lon_deg, 6));
    GO.lcd.println("Lat_rad:");
    GO.lcd.println(String(lat_rad, 6));
    GO.lcd.println("Zoom:");
    GO.lcd.println(String(zoom));

    Serial.println(String(tileX, 6));
    Serial.println(String(tileY, 6));
    Serial.println(String(posX));
    Serial.println(String(posY));
    Serial.println(zoom);
    Serial.println(String(lon_deg, 6));
    Serial.println(String(lat_rad, 6));

    old_tileX = tileX;
    old_tileY = tileY;
  }

  old_lat_rad = lat_rad;
  old_lon_deg = lon_deg;
  old_zoom = zoom;
}

double long2tilex(double lon, double z)
{
  return (double)((lon + 180.0) / 360.0 * pow(2.0, z));
}

double lat2tiley(double lat, double z)
{
  return (double)((1.0 - log( tan(lat * M_PI / 180.0) + 1.0 / cos(lat * M_PI / 180.0)) / M_PI) / 2.0 * pow(2.0, z));
}

double tilex2long(int x, int z)
{
  return x / pow(2.0, z) * 360.0 - 180;
}

double tiley2lat(int y, int z)
{
  double n = M_PI - 2.0 * M_PI * y / pow(2.0, z);
  return 180.0 / M_PI * atan(0.5 * (exp(n) - exp(-n)));
}

Reference

https://forum.odroid.com/viewtopic.php?f=162&t=33629 https://youtu.be/-4kA_KhIvus https://github.com/ripper121/OdroidGoOSMGPSOffline https://youtu.be/BQWwTZANGlE

Be the first to comment

Leave a Reply