Displays distance to start location on OLED screen.

Dependencies:   mbed

Revision:
0:972874f31c98
diff -r 000000000000 -r 972874f31c98 main.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Wed Mar 07 12:49:14 2018 +0000
@@ -0,0 +1,316 @@
+/*
+Reads UBX NAV-PVT messages from a u-blox GPS module and displays the distance in
+centimeters between the current location and the first location received at power
+up. No configuration messages are sent to the GPS so it needs to be prepared to
+output NAV-PVT messages ONLY, and have that configuration saved so it will always
+do so when powered up.
+Screen is a SSD1306 running on I2C connection.
+*/
+#include "mbed.h"
+#include "Clock.h"
+#include "u8g.h"
+
+#define OLED_ADDRESS 0x78
+
+DigitalOut myled(PB_1); 
+I2C i2c(PB_7, PB_6); // sda, scl
+Serial gps(PA_2, PA_3, 19200); // tx, rx
+
+// At 19200 baud the buffer gets overwritten too quickly. We need to make our
+// own software buffer and update it by interrupts so we don't miss anything.
+void Rx_interrupt();
+const int buffer_size = 1024;
+char rx_buffer[buffer_size+1];
+volatile int rx_in=0;
+volatile int rx_out=0;
+
+
+void u8g_Delay(uint16_t usec)
+{
+    wait_us((int)usec);
+}
+
+void u8g_MicroDelay()
+{
+    wait_us(1);
+}
+
+void u8g_10MicroDelay()
+{
+    wait_us(10);
+}
+
+uint8_t u8g_com_hw_i2c_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr)
+{
+    static uint8_t control = 0;
+
+    switch(msg) {
+        case U8G_COM_MSG_STOP:
+            break; //do nothing...
+
+        case U8G_COM_MSG_INIT:
+            u8g_MicroDelay();
+            break;
+
+        case U8G_COM_MSG_ADDRESS:
+            //the control byte switches the mode on the device and is set here
+            u8g_10MicroDelay();
+            if (arg_val == 0) {
+                control = 0;
+            } else {
+                control = 0x40;
+            }
+            break;
+
+        case U8G_COM_MSG_WRITE_BYTE: {
+            //simple: just write one byte
+            char buffer[2];
+            buffer[0] = control;
+            buffer[1] = arg_val;
+            i2c.write(OLED_ADDRESS, buffer, 2);
+        }
+        break;
+
+        case U8G_COM_MSG_WRITE_SEQ:
+        case U8G_COM_MSG_WRITE_SEQ_P: {
+            char buffer[1024];
+            char *ptr = (char*)arg_ptr;
+            buffer[0] = control;
+            for (int i = 1; i <= arg_val; i++) {
+                buffer[i] = *(ptr++);
+            }
+            i2c.write(OLED_ADDRESS, buffer, arg_val);
+        }
+        break;
+
+    }
+    return 1;
+}
+
+#ifndef DEGTORAD
+#define DEGTORAD 0.0174532925199432957f
+#define RADTODEG 57.295779513082320876f
+#endif
+
+struct geoloc {
+    double lat;
+    double lon;
+    geoloc(double x = 0, double y = 0, int32_t a = 0) {
+        lat = x;
+        lon = y;
+    }
+};
+
+// http://www.movable-type.co.uk/scripts/latlong.html
+float geoDistance(struct geoloc &a, struct geoloc &b)   // returns meters
+{
+    const double R = 6371000; // km
+    double p1 = a.lat * DEGTORAD;
+    double p2 = b.lat * DEGTORAD;
+    double dp = (b.lat-a.lat) * DEGTORAD;
+    double dl = (b.lon-a.lon) * DEGTORAD;
+
+    double x = sin(dp/2) * sin(dp/2) + cos(p1) * cos(p2) * sin(dl/2) * sin(dl/2);
+    double y = 2 * atan2(sqrt(x), sqrt(1-x));
+
+    return R * y;
+}
+
+geoloc originLocation;
+geoloc currentLocation;
+
+const unsigned char UBX_HEADER[] = { 0xB5, 0x62 };
+
+struct NAV_PVT {
+    unsigned char cls;
+    unsigned char id;
+    unsigned short len;
+    unsigned long iTOW;          // GPS time of week of the navigation epoch (ms)
+
+    unsigned short year;         // Year (UTC)
+    unsigned char month;         // Month, range 1..12 (UTC)
+    unsigned char day;           // Day of month, range 1..31 (UTC)
+    unsigned char hour;          // Hour of day, range 0..23 (UTC)
+    unsigned char minute;        // Minute of hour, range 0..59 (UTC)
+    unsigned char second;        // Seconds of minute, range 0..60 (UTC)
+    char valid;                  // Validity Flags (see graphic below)
+    unsigned long tAcc;          // Time accuracy estimate (UTC) (ns)
+    long nano;                   // Fraction of second, range -1e9 .. 1e9 (UTC) (ns)
+    unsigned char fixType;       // GNSSfix Type, range 0..5
+    char flags;                  // Fix Status Flags
+    unsigned char reserved1;     // reserved
+    unsigned char numSV;         // Number of satellites used in Nav Solution
+
+    long lon;                    // Longitude (deg)
+    long lat;                    // Latitude (deg)
+    long height;                 // Height above Ellipsoid (mm)
+    long hMSL;                   // Height above mean sea level (mm)
+    unsigned long hAcc;          // Horizontal Accuracy Estimate (mm)
+    unsigned long vAcc;          // Vertical Accuracy Estimate (mm)
+
+    long velN;                   // NED north velocity (mm/s)
+    long velE;                   // NED east velocity (mm/s)
+    long velD;                   // NED down velocity (mm/s)
+    long gSpeed;                 // Ground Speed (2-D) (mm/s)
+    long heading;                // Heading of motion 2-D (deg)
+    unsigned long sAcc;          // Speed Accuracy Estimate
+    unsigned long headingAcc;    // Heading Accuracy Estimate
+    unsigned short pDOP;         // Position dilution of precision
+    short reserved2;             // Reserved
+    unsigned long reserved3;     // Reserved
+
+    // Most receivers prior to the M8 series have an older firmware with a
+    // smaller size NAV_PVT message. For Neo-7M you should comment this out.
+    uint8_t dummy[8];
+};
+
+NAV_PVT pvt;
+
+void Rx_interrupt()
+{
+    while ((gps.readable()) && (((rx_in + 1) % buffer_size) != rx_out)) {
+        rx_buffer[rx_in] = gps.getc();
+        rx_in = (rx_in + 1) % buffer_size;
+    }
+}
+
+bool gpsAvailable()
+{
+    return rx_in != rx_out;
+}
+
+char gpsRead()
+{
+    __disable_irq();
+    char c = rx_buffer[rx_out];
+    rx_out = (rx_out + 1) % buffer_size;
+    __enable_irq();
+    return c;
+}
+
+void calcChecksum(unsigned char* CK)
+{
+    memset(CK, 0, 2);
+    for (int i = 0; i < (int)sizeof(NAV_PVT); i++) {
+        CK[0] += ((unsigned char*)(&pvt))[i];
+        CK[1] += CK[0];
+    }
+}
+
+bool processGPS()
+{
+    static int fpos = 0;
+    static unsigned char checksum[2];
+    const int payloadSize = sizeof(NAV_PVT);
+
+    while ( gpsAvailable() ) {
+        char c0 = gpsRead();
+        unsigned char c = *(unsigned char*)(&c0);
+        if ( fpos < 2 ) {
+            if ( c == UBX_HEADER[fpos] )
+                fpos++;
+            else
+                fpos = 0;
+        } else {
+            if ( (fpos-2) < payloadSize )
+                ((unsigned char*)(&pvt))[fpos-2] = c;
+
+            fpos++;
+
+            if ( fpos == (payloadSize+2) ) {
+                calcChecksum(checksum);
+            } else if ( fpos == (payloadSize+3) ) {
+                if ( c != checksum[0] )
+                    fpos = 0;
+            } else if ( fpos == (payloadSize+4) ) {
+                fpos = 0;
+                if ( c == checksum[1] ) {
+                    myled = 1 - myled;
+                    return true;
+                }
+            } else if ( fpos > (payloadSize+4) ) {
+                fpos = 0;
+            }
+        }
+    }
+    return false;
+}
+
+u8g_t u8g;
+
+double distance = 0;
+int numSV = 0;
+float hAcc = 0;
+uint64_t lastScreenUpdate = 0;
+char spinnerBuf[16];
+char satsBuf[32];
+char haccBuf[32];
+char distBuf[32];
+
+char* spinnerChars = "/-\\|";
+uint8_t screenRefreshSpinnerPos = 0;
+uint8_t gpsUpdateSpinnerPos = 0;
+
+void draw(void)
+{
+    u8g_SetFont(&u8g, u8g_font_9x15B);
+    u8g_DrawStr(&u8g, 2, 12, spinnerBuf);
+
+    u8g_SetFont(&u8g, u8g_font_helvB08);
+    u8g_DrawStr(&u8g, 42, 8, satsBuf);
+    u8g_DrawStr(&u8g, 42, 21, haccBuf);
+
+    u8g_SetFont(&u8g, u8g_font_fub30);
+    u8g_DrawStr(&u8g, 2, 62, distBuf);
+}
+
+void updateScreen()
+{
+    sprintf(spinnerBuf, "%c %c", spinnerChars[screenRefreshSpinnerPos], spinnerChars[gpsUpdateSpinnerPos]);
+    sprintf(satsBuf, "Sats: %d", numSV);
+    sprintf(haccBuf, "hAcc: %d", (int)hAcc);
+    sprintf(distBuf, "%.1lf", distance);
+
+    u8g_FirstPage(&u8g);
+    do {
+        draw();
+    } while ( u8g_NextPage(&u8g) );
+    u8g_Delay(10);
+}
+
+void checkScreenUpdate()
+{
+    uint64_t now = clock_ms();
+    if ( now - lastScreenUpdate > 100 ) {
+        updateScreen();
+        lastScreenUpdate = now;
+        screenRefreshSpinnerPos = (screenRefreshSpinnerPos + 1) % 4;
+    }
+}
+
+int main()
+{
+    gps.attach(&Rx_interrupt, Serial::RxIrq);
+    u8g_InitComFn(&u8g, &u8g_dev_ssd1306_128x64_i2c, u8g_com_hw_i2c_fn);
+
+    while ( ! processGPS() )
+        checkScreenUpdate();
+
+    originLocation.lat = pvt.lat * 0.0000001;
+    originLocation.lon = pvt.lon * 0.0000001;
+
+    while (true) {
+
+        if ( processGPS() ) {
+            currentLocation.lat = pvt.lat * 0.0000001;
+            currentLocation.lon = pvt.lon * 0.0000001;
+            numSV = pvt.numSV;
+            hAcc = pvt.hAcc;
+            distance = geoDistance( currentLocation, originLocation ) * 100;
+            gpsUpdateSpinnerPos = (gpsUpdateSpinnerPos + 1) % 4;
+        }
+
+        checkScreenUpdate();
+    }
+}
+