Ray Burnette
Published

Maple Mini GPS Clock: Color GLCD and Barometric Graphing

GPS color clock with BMP180 barometric pressure sensor running on a 90-MIP ARM STM32F103 all programmed under the Arduino GUI 1.6.1.

IntermediateFull instructions provided4,115
Maple Mini GPS Clock: Color GLCD and Barometric Graphing

Things used in this project

Hardware components

BMP180 breakout module 3.3V
×1
Maple Mini Clone STM32F103
Many Chinese sellers distribute this produce - link is not an endorsement
×1
Vishay TCRT5000 Reflective Optical Sensor
×1
ILI9341 240x320 SPI GLCD
SPI 3.3V - the SD card feature is NOT utilized
×1
Enclosure - Plastic with Clear Front
Purchased locally in a discount/overstock store
×1
Rechargeable-Battery-Pack-Cable
These are often found at extreme discount, mine came with the USB cord
×1

Story

Read more

Code

file_14366.txt

C/C++
GPS_Time_Baro.ino
/*Please review License.h tab for use/rights information regarding included libraries
  GPS_Time_Baro based on original sketch by M. Ray Burnette 3/02/2014
  Arduino 1.6.1 - Maple Mini Port STM32F103 by Ray Burnette 3/09/2015 PUBLIC DOMAIN by Author
    Sketch uses 46,360 bytes (42%) of program storage space. Maximum is 108,000 bytes.
    Global variables use 5,752 bytes of dynamic memory.
*/

#include <SPI.h>                                     // \Documents\Arduino\hardware\STM32\STM32F1\libraries\SPI
#include <Wire.h>                                    // I2C \Documents\Arduino\hardware\STM32\STM32F1\libraries\Wire (legacy)
#include <Streaming.h>                               // \Documents\Arduino\libraries\Streaming (legacy)

#include ".\BMP085.h"                                // #include "I2Cdev.h" is pulled in also
#include ".\Adafruit_GPS.h"
#include ".\Adafruit_GFX.h"
#include ".\Adafruit_ILI9341.h"
#include ".\SoftwareSerial.h"                        // faux version only for STM32 Maple
#include ".\Utilities.h"

/*  History array fills from left and element [80] is dropped every nn minutes (average calc period)
 0         1         2         3         4         5         6         7         8
 012345678901234567890123456789012345678901234567890123456789012345678901234567890  */
long pHistory[] = {
        0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L,
        0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 
        0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 
        0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L };

int hPa10, Fah10;                                    // used for Nokia LCD integer to character - faux decimal place
int localCorrect = 3400;                             //  +/- hPa*100 to correct for local altitude: 100X delta:  3400== Briscoe Field, Ga
float temperature;
float pressure;                                      // How many hPa in 1 mm Hg? The answer is 1.3332239
float inHg;                         
float mmhg_conversion= 3386.388140071641;            // convert hPa to inches Hg
float altitude;
int32_t lastMicros;
byte error, address        = 77;                     // I2C address specific to BMP180
unsigned long TimeMarker;
unsigned long PlotTime;                              // trigger point to store Barometric reading
const unsigned long GraphResponsemS = 540000 ;       // 9 minutes per array element,  9*80 = 720 minutes = 12 hours total

uint8_t last_seconds = 0;
uint8_t TimeZoneHour = 5;                            // Affected as -1 for DST
uint8_t LocalHour    = 0;
uint8_t LocalMinute  = 0;
uint8_t LocalSecond  = 0;
uint8_t LocalMonth   = 0;
uint8_t LocalDay     = 0;
uint8_t LocalYear    = 0;

uint8_t increment = 0;
boolean flip = true;
float   Celsius = 0.0;
boolean PM = true;
boolean DST = true;                                  // Daylight Savings Time: Pin A3 DST = LOW Std time = HIGH
boolean FirstTime = true;                            // Before satellite time is available via $GPRMC or $GPGAA
boolean Fahrenheit = true;                           // set by digital pin D12
boolean ForcedUpdate = false;                        // used to force a time+date screen full redraw

// ILI9341 TFT GLCD display connections for hardware SPI
// Signal           Maple Mini         Leonardo      LCD Display    UNO pins
//#define _sclk         6         //         15       J2 pin 7          13
//#define _miso         5 NC      //         14          pin 9          12
//#define _mosi         4         //         16          pin 6          11
#define TFT_CS         13         //         10          pin 3          10
#define TFT_DC         12         //          9          pin 5           9
#define TFT_RST        14         //          8          pin 4           8

// create lcd object
Adafruit_ILI9341 lcd = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); // Using hardware SPI

// For Maple Mini STM32F103 the hacked SoftwareSerial.h & SoftwareSerial.cpp must be in this sketch
// create mySerial object for exclusive use by Adafruit_GPS
SoftwareSerial mySerial(0, 1);                                    // Rx Tx : Connect the GPS TX (transmit) pin 0 and leave pin 1 unconnected
Adafruit_GPS GPS(&mySerial);                                      // create GPS object
BMP085 barometer;                                                 // create barometer object


void setup()  {
  lcd.begin();
  mySerial.begin(9600);
  Wire.begin(); barometer.initialize();                           // I2C Stuff
  lcd.setRotation(3);                                             // landscape with SPI conn J2 to left side.
  lcd.fillScreen(ILI9341_BLACK);
  lcd.setCursor(0, 0);
  lcd.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
  // lcd.setTextSize(1);                                          // Tiny   53 char / line
  // lcd.setTextSize(2);                                          // Small  26 char / line
  lcd.setTextSize(3);                                             // Medium 17 char / line  10Pixels Wide x 25 Pixels / line
  //lcd.setTextSize(4);                                           // Large  13 char / line
  Wire.beginTransmission(address);                                // I2C Stuff
  error = Wire.endTransmission();

  lcd.setCursor(0, 0);
  if(barometer.testConnection())
  {
    lcd.setTextColor(ILI9341_GREEN, ILI9341_BLACK);
    lcd.print("I2C: OK   V150309");
  } else {
    lcd.print("BMP180: Failure!");
    for(;; ) {;}                                                  // Error with infinite loop
  }

   pinMode( 18, INPUT);                                           // DST hi = NO low = YES
   digitalWrite( 18, HIGH);                                       // turn on pullup resistor

  if( digitalRead(18) ) {                                         // PB4 low for Daylight Savings Time
    DST = false;                                                  // Default
  } else {
    DST = true;
    TimeZoneHour = TimeZoneHour - 1;
  }
  pinMode(BOARD_BUTTON_PIN, INPUT);                               // board button (for manual History select)
  lcd.drawFastHLine(0, 23, 319, ILI9341_RED);                     // Hello Screen
  lcd.setTextColor(ILI9341_CYAN, ILI9341_BLACK);
  lcd.setCursor(0, 25);
  lcd.print("by: Ray Burnette");
  lcd.setCursor(0, 50);
  lcd.println("(c) GPS timedate");
  lcd.drawFastHLine(0, 85, 319, ILI9341_GREEN);
  lcd.setTextColor(ILI9341_GREEN, ILI9341_BLACK);
  lcd.setCursor(0, 90);
  lcd.print( "Waiting on GPS...");
  lcd.print("");
  lcd.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
  lcd.print( "Up to 30+ minutes");
  lcd.println("on first-time use");
  lcd.drawFastHLine(0, 175, 319, ILI9341_BLUE);
  lcd.setTextSize(2);                                             //  26 char / line
  lcd.setCursor (20, 180);
  lcd.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);
  lcd.print("GPS input visible below:");
  lcd.drawRect(0, 0, 319, 200, ILI9341_WHITE);
  lcd.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
  lcd.setTextSize(3);
  lcd.setCursor(0, 220);
  lcd.setTextWrap(false);
}


void loop()  {
  char c = GPS.read();                                            // read data from the GPS and check for full sentence
  if (GPS.newNMEAreceived()) {
    if (!GPS.parse(GPS.lastNMEA()));                              // sets the newNMEAreceived() flag: return; no longer used
  }

  if ((last_seconds != GPS.seconds) || ForcedUpdate)              // Except for seconds, other screen data only once per minute
  {
    if (FirstTime)     { firsttime(); }                           // Do this once to notify the user
    // update with new satellite data
    last_seconds =  GPS.seconds;
    LocalHour    =  GPS.hour;
    LocalMinute  =  GPS.minute;
    LocalSecond  =  GPS.seconds;
    LocalMonth   =  GPS.month;
    LocalDay     =  GPS.day;
    LocalYear    =  GPS.year;

    // This section always updates every second EXCEPT immediately upon return from barometer display
    if (! ForcedUpdate)                                           // EXCEPT do not show seconds immediately after Barometric viewing
    {
      lcd.setTextSize(4);
      lcd.setTextColor( ILI9341_YELLOW, ILI9341_BLACK );
      // lcd.setTextColor( random(0xff, 0xffff ) );               // cool, but disturbing
      lcd.setCursor( 138, 30);
    
      if(LocalSecond < 10)
      {
        lcd.print("0");                                           // position  12
        lcd.print(LocalSecond);                                   // position  13
      } else {
        lcd.print(LocalSecond);                                   // positions 12, 13
      }
    }

    if(LocalHour >= TimeZoneHour)
    {
      LocalHour = LocalHour - TimeZoneHour;
    } else {
      LocalHour = (LocalHour + 24) - TimeZoneHour;                // Correct for period when London is a day ahead
      LocalDay  = LocalDay - 1;

      if( LocalDay == 0)                                          // backward a month
      {
          LocalMonth = LocalMonth - 1;
          LocalDay   = DaysInMonth[ LocalMonth - 1 ];             // index 0 based
      }

      // The above may need correcting... (brute force)
      // January in London still December Westward
      if( LocalMonth == 0)                                        // GPS months are 01 through 12
      {
        LocalMonth = 12;
        LocalDay   = DaysInMonth[ 11 ];                           // lastday of December
      }
      else if( (LocalMonth == 2) && (LocalYear % 4 == 0) && (LocalDay == 28) )
      {
        LocalDay = LocalDay + 1;                                  // Deal with LocalMonth = 2 and leapyear (simple... not exhaustive)
      }
    }

    if(LocalHour >= 12)                                           // Military or Civilian time selection
    {
      PM = true;
      //if(LocalHour > 12) LocalHour = LocalHour - 12;            // non-Mil time
    } else {
      PM = false;
    }

    barometer.setControl(BMP085_MODE_TEMPERATURE);                // request temperature
    lastMicros = micros();
    while (micros() - lastMicros < barometer.getMeasureDelayMicroseconds()); // wait for conversion (4.5ms delay)

    if (Fahrenheit) {                                             // read calibrated temperature value in degrees Fahrenheit
      temperature = barometer.getTemperatureF();
    } else {
      temperature = barometer.getTemperatureC();
    }

    lastMicros = micros();
    barometer.setControl(BMP085_MODE_PRESSURE_3);                 // request pressure (3x oversampling mode, high detail, 23.5ms delay)
    while (micros() - lastMicros < barometer.getMeasureDelayMicroseconds());

    pressure = barometer.getPressure();                           // read calibrated pressure value in Pascals (Pa)
    pressure += localCorrect;
    inHg = float ( pressure * 10.0 / mmhg_conversion);

    // Display to GLCD
    if(LocalSecond == 0 || ForcedUpdate)                          // full screen update every minute or on-demand
    {
      if (ForcedUpdate)  { ForcedUpdate = false; }
      lcd.fillScreen(ILI9341_BLACK);                              // Screen clear GLCD
      lcd.setTextSize(4);
      lcd.setTextColor( ILI9341_CYAN );
      lcd.setCursor( 0, 0);
      temperature += 0.5;                                         // round up
      lcd.print( temperature, 0 );                                // positions 0, 1, 2? no decimal

      if (Fahrenheit) {
        lcd.print(" F");                                          // positions 2
      } else {
        lcd.print(" C");                                          // positions 2
      }

      // Middle pane
      lcd.setTextColor( ILI9341_GREEN );
      lcd.setTextSize(10);   
      lcd.setCursor( 10, 80);
      if(LocalHour < 10) lcd.print (" ");                         // position  6
      lcd.print(LocalHour);                                       // positions 6, 7
      lcd.print(":");                                             // position  8

      if(LocalMinute < 10)
      {
        lcd.print("0");                                           // position  9
        lcd.print(LocalMinute);                                   // position  10
       } else {
          lcd.print(LocalMinute);                                 // positions 9, 10
       }

      // Day-Of-Week, Month and Date info
      lcd.setTextColor(ILI9341_YELLOW);
      lcd.setTextSize(4);
      // Day Of Week is shown in Top pane, right
      lcd.setCursor(235, 0);                                      // upper section, right
      lcd.print(DayOfWeek[dow(LocalYear, LocalMonth, LocalDay)]); // DayOfWeek
      // Month Name and Date and Year are shown in lower pane
      lcd.setCursor(35, 195);
      lcd.print(NameOfMonth[LocalMonth - 1]);                     // positions 6 - 8
      lcd.print(" ");                                             // position  9
      if(LocalDay < 10) {
        lcd.print(" ");                                           // position  10
        lcd.print(LocalDay);
      } else {
        lcd.print(LocalDay);                                      // positions 10, 11
      }
      lcd.print(" 20");                                           // positions 13, 14
      lcd.print(LocalYear);                                       // positions 15, 16
    }
  }                                                               // above code only excutes once-per-minute unless forced update

  // littl' micro-terminal below to display GPS serial output until GPS data is valid (mostly $GPGLL sentences)
  if (FirstTime && (c != 0))  {
    if ( c == 36 )  {
       lcd.setCursor(0, 220);
       lcd.print( "                  " );
       lcd.setCursor(0, 220);
     }
     lcd.print (c);
  }

  if ((TimeMarker + GraphResponsemS) < millis() )
  {                                                               // machine clock has advanced beyond trigger, so store current pressure
      TimeMarker = millis();                                      // reset trigger for future (next) event
      StoreHistory();                                             // update pHistory matrix
  }
  
  if (isButtonPressed()) displayHistory();                        // Intent is to only display on command
} // loop


void firsttime( void ) {                                          // First good time/date since power on?
    FirstTime     = false;                                        // GPS is initialized
    ForcedUpdate  = true;                                         // Force a refresh of the main time/date display screen
}


void StoreHistory ( void ) {                                      // Creates a boxed border using lines 0 & 1
  long M;                                                         // pressure/100 --> hPa
  int n;                                                          // avoid compiler complaints in for(;;) loop

  inHg = float ( M / mmhg_conversion) ;
  M = pressure / 100L;                                            // scale to hPa

  // graph values are typically between a low of 985 and a high of 1040
  // clipping high/low forces the dynamic range to be limited to a maximum change of 45 hPa
  if (M > 1040L)  M = 1040L;                                      // clip (flatten) ceiling
  if (M < 985L)   M =  985L;                                      // clip (flatten) floor

  // array [0] is reserved for newest value; Therefore [80] = [79], [79] = [78],..., [1]==[0] after shift
  for (n = 80; n > 0; n--)                                        // shift history array to the right, oldest value [80] is lost
  {
    pHistory[n] = pHistory[(n - 1)];                              // shift data right, oldest value lost, [1] becomes [0] the current value
  }
  pHistory[0] = M;                                                // save current "adjusted" pressure hPa [0]
}


void displayHistory( void ) {
  long       M;
  long       minimum = 1040L;
  uint8_t    h, z;
  uint16_t   x, y;
  int        temp1, temp2;                                        // working vars for type conversion

  M = pressure / 100L;                                            // scale to hPa
  pHistory[0] = M;                                                // update pHistory[0] as StoreHistory() may never have been called
  
  lcd.fillScreen(ILI9341_BLACK);
  lcd.setCursor(0, 1); lcd.print("hPa    inHg");
  lcd.setCursor(0, 35); lcd.print(pHistory[0]);
  lcd.print("   ");
  temp1 = inHg / 10;                                              // Integer component
  lcd.print(temp1); lcd.print(".");                               // decimal place
  temp2 = ((inHg / 10) - (float) temp1 ) * 100;                   // 2 decimal places
  lcd.print(temp2);                                               // print it as an integer
  lcd.drawFastVLine(0,  88, 112, ILI9341_RED);                    // setup Y axis
  lcd.drawFastHLine(0, 200, 238, ILI9341_RED);                    // setup X axis
  lcd.drawFastHLine(0, 201, 238, ILI9341_RED);                    // setup X axis double-width X axis for emphasis
  lcd.setTextSize(1);
  lcd.setCursor(240,  70); lcd.print("_ 1040= 30.71");          // setup Y legends
  lcd.setCursor(240, 101); lcd.print("_ 1026= 30.30");
  lcd.setCursor(240, 132); lcd.print("_ 1012= 29.88");
  lcd.setCursor(240, 163); lcd.print("__ 998= 29.47");
  lcd.setCursor(240, 194); lcd.print("__ 985= 29.09");
  // tiny fonts for timeline legend @53 char/line (6px/char)
  lcd.setCursor(0, 205); lcd.print("N        ^         ^         ^         ^  Historical ");
  lcd.setCursor(0, 213); lcd.print("O        0         0         0         1  Timeline   ");
  lcd.setCursor(0, 221); lcd.print("W        3         6         9         2  In Hours   ");

  z = 0;                                                          // z will span 0 to 319, full screen landscape width in pixels
  for ( x = 1; x < 240; x++ )  {                                  // 80 elements @1 element per 9 minutes = 20 elements per 3 hours
    if ( x % 3 == 0 ) {                                           // this provides for skipping every 3th position for bar spacing
      z++;
    } else if (pHistory[z] != 0) {                                // array is initially init to zero value, no need to plot this
      if (pHistory[z] < minimum) minimum = pHistory[z];           // capture the lowest 12 hour pressure reading
      h = (pHistory[z] - 985) * 2;                                // h is for height and x2 is for scaling factor
      y = 200 - h;                                                // Adafruit's GFX y coordinates plots upside down, flip reference
      lcd.drawFastVLine(x, y, h, ILI9341_CYAN);                   // 2 bars are drawn per array element and 1 blank space for separation
    }
  }

  y = 200 - ((minimum - 985) * 2);
  lcd.drawFastHLine(1, y, 238, ILI9341_MAGENTA);                    // draw 12 hour lowest reading
  ForcedUpdate = true;                                              // Force main screen full update upon return from this function
  delay(5000);
}

Credits

Ray Burnette

Ray Burnette

57 projects • 393 followers
IT architect - retired AT&T... USAF-Secure Comm , Burroughs, Clemson U. School of EE Research, Southern Bell, BellSouth, SofKinetics, Inc. (President), AT&T IT
Contact

Comments