Return to Robotics Tutorials

GPS with I2C interface

u-blox NEO-M8N

Robots often integrate a GPS module to enable navigation. The majority of integrated GPS receivers provide a UART interface, but unfortunately this is not a convenient sensor interface for robots integrating a multitude of functions. This article explains how to make an I2C interface for your GPS using an ATtiny as a UART-to-I2C bridge.

I2C interface for GPS

For background material on interfacing the UART-based GPS module with an ATtiny, please see the GPS with SPI interface article. The following article and associated code was written to interface with a u-blox NEO-M8N, but it is likely to work for many other similar modules that come with a simple UART interface.

This article expands on the previous article but alters the interface from SPI to I2C. I2C is far more convenient to use on robots than UART or SPI -- the main reason being that a large numberof I2C sensors can share the same physical pins on the master device. Many processors (such as the Raspberry Pi) only include direct support for 2 SPI slave devices though more can be interfaced with some additional work. UART is generally even more limited in that most processors only support a single hardware UART interface. If your robot has a multitude of sensors, I2C is a great solution in that two hardware pins can address a dozen or more sensors (support for more using I2C repeaters / buffers).

NOTE: The remainder of this article will assume you have read the SPI interface article and only cover the differences with I2C.

GPS logging on Raspberry Pi via I2C interface from ATtiny

GPS UART-to-I2C Bridge with ATtiny

In a similar manner as described in the GPS with SPI article, a UART-to-I2C bridge will convert the frequent NMEA messages from the serial interface into a set of coherent holding registers within the ATtiny. On the other side of the ATtiny bridge is an I2C interface to an I2C master (such as a Raspberry Pi 3).

Using an ATtiny167 (Digispark Pro clone), the total cost of the I2C-to-UART GPS interface is approximately $3! The ATtiny167 is a small AVR microcontroller that provides hardware support for I2C as well as UART, meaning that we don't need to write any bit-banging code to implement these interfaces.

ATtiny I2C slave interface

The GPS parser code records the decoded fields (such as GPS coordinates, date/time, ground speed, etc.) into individual sensor variables. We then transfer each of these variables into a set of I2C holding registers. The I2C slave code will monitor the external I2C interface and provide access to the I2C holding registers.

An additional I2C register is used for status -- it contains the VALID bit that we use to ensure we have coherent access to the registers.

NOTE: Article content under construction... please check back soon!

C Code for GPS UART-to-I2C interface on ATtiny


// ======================================================
// GPS UART-to-I2C interface for ATtiny
//   by Calvin Hass
//   http://www.impulseadventure.com/elec/
//
// This C code implements a UART receive interface that
// captures the incoming GPS NMEA messagse into a set of
// holding register that can then be accessed coherently
// by an external I2C master (effectively a UART-to-I2C
// bridge). GPS parsing is limited to the RMC sentence which
// conveys the basic information of interest including: location,
// date/time, speed, course, etc.
//
// The I2C slave code also provides support for working
// around the Raspberry Pi's I2C clock stretching bug (enabled
// by uncommenting "#define I2C_RD_STRETCH_EN").
//
// ======================================================
//
// REVISION 1.0:
// - Initial release
//
// NOTES:
// - The following has been tested to run on an ATtiny167 bridging
//   between a Raspberry Pi and a u-blox NEO-M8N.
// - This ATtiny has a "LIN" module that provides the hardware UART
//   interface; some small modifications will be required to use
//   with microcontrollers (such as ATtiny85) that provide a "USI"
//   instead of the "LIN".
//
// ======================================================


// ------------------------------------------------------
// I2C Register Description
// ------------------------------------------------------
// Reg 0x00: Status[7:0]
//           - Status[7] = GPSValid (Write-1-to-clear)
//           - Status[6] = GPSFix
//           - Status[5] = GPS UART CRC OK
// Reg 0x01: Rsvd
// Reg 0x02: Fixed = 0x12
// Reg 0x03: Fixed = 0x34
// Reg 0x04: GPS:Time[15:8]
// Reg 0x05: GPS:Time[7:0]
// Reg 0x06: GPS:Msecs[15:8]
// Reg 0x07: GPS:Msecs[7:0]
// Reg 0x08: GPS:Knots[15:8]
// Reg 0x09: GPS:Knots[7:0]
// Reg 0x0A: GPS:Course[15:8]
// Reg 0x0B: GPS:Course[7:0]
// Reg 0x0C: GPS:Date[15:8]
// Reg 0x0D: GPS:Date[7:0]
// Reg 0x0E: GPS:Lat[31:24]
// Reg 0x0F: GPS:Lat[23:16]
// Reg 0x10: GPS:Lat[15:8]
// Reg 0x11: GPS:Lat[7:0]
// Reg 0x12: GPS:Long[31:24]
// Reg 0x13: GPS:Long[23:16]
// Reg 0x14: GPS:Long[15:8]
// Reg 0x15: GPS:Long[7:0]
// Reg 0x16: GPS:ChkVal[7:0]
// Reg 0x17: GPS:ChkSum[7:0]
// Reg 0x18: Rsvd
// Reg 0x19: Rsvd
// Reg 0x1A: Rsvd
// Reg 0x1B: I2C_Rd_Delay[7:0]
// ------------------------------------------------------


// Reference libraries leveraged:
// - TinyWireS for I2C slave interface
// - Minimal GPS Parser (http://www.technoblogy.com/show?SJ0) [modified here]

// For I2C operation
#include <TinyWireS.h>

// For general interrupts
#include <avr/interrupt.h>
#include <avr/io.h>

// For UART serial operation
#include <HardwareSerial.h>

  
// --------------------------------------------------
// Configuration: General
// --------------------------------------------------

  // I2C slave device address
  #define I2C_SLAVE_ADDRESS   0x28

  // UART baud rate. This should match the GPS.
  #define UART_RATE           38400

  // Enable LED flashing of status
  #define LED_FLASH_EN            // Flash LED at boot and during runtime (comment out to disable)
  #define LED_FLASH_GPS_EN        // Flash LED upon successful GPS fix (comment out to disable)
  #define LED_FLASH_PERIOD  3000  // Time between status LED flashes (in ms)

  // I2C Clock Stretching default
  // - The following #define will enable the insertion of specific
  //   delays that can help work around I2C masters that have issues
  //   with clock stretching (such as the Raspberry Pi / Pi 2 / Pi 3).
  // - If enabled, the I2C_RD_STRETCH_DEFAULT can be configured to
  //   provide a specific delay (counting pairs of clock cycles) that
  //   will work for the master at a given I2C frequency. This value
  //   can also be overwritten by the master at run-time by writing to
  //   I2C register REG_I2C_CTRL(low byte) / 0x1B.
  #define I2C_RD_STRETCH_EN
  #define I2C_RD_STRETCH_DEFAULT  0x10

  // Direct PORT register access
  // - Define the PORTx and DDRx registers and bits that are
  //   used for the GPIO functions. This allows for much faster
  //   pin access than using digitalWrite().
  // - NOTE: These definitions work for an ATtiny167 microcontroller,
  //   but they may require modification to match the PORT-pin
  //   mapping of other devices.

  // Set Pin State macros
  // - Equivalent to "digitalWrite(x,<HIGH/LOW>)"
  #define SETPIN1_LED_H()     PORTB |=  (1<<PORTB1)
  #define SETPIN1_LED_L()     PORTB &= ~(1<<PORTB1)


  // Set Pin Direction macros
  // - Equivalent to "pinMode(x,<OUT/IN>)"
  #define SETPIN1_LED_DIR_O() DDRB  |=  (1<<DDB1)


// --------------------------------------------------
// General purpose macros
// --------------------------------------------------

// Define NOP macro for delay loop
#define NOP __asm__ __volatile__ ("nop\n\t")


// --------------------------------------------------
// Configuration: I2C Registers
// --------------------------------------------------

// Byte address definitions for 16-bit I2C registers
#define         REG_STATUS        0x00  // Indicate status of GPS data in registers
#define         REG_FIXED         0x02  // Fixed value (0x1234) for debug
#define         REG_GPS_TIME      0x04  // GPS: Time (hours*100 + minutes)
#define         REG_GPS_MSECS     0x06  // GPS: Time (seconds*1000 + milliseconds)
#define         REG_GPS_KNOTS     0x08  // GPS: Speed over ground
#define         REG_GPS_COURSE    0x0A  // GPS: Course over ground
#define         REG_GPS_DATE      0x0C  // GPS: Date (month*100 + days)
#define         REG_GPS_LAT_H     0x0E  // GPS: Latitude (upper word)
#define         REG_GPS_LAT_L     0x10  // GPS: Latitude (lower word)
#define         REG_GPS_LONG_H    0x12  // GPS: Longitude (upper word)
#define         REG_GPS_LONG_L    0x14  // GPS: Longitude (lower word)
#define         REG_GPS_CHECK     0x16  // GPS: Checksum expected and actual
#define         REG_EMPTY         0x18  // Blank read/write register for debug
#define         REG_I2C_CTRL      0x1A  // I2C Clock Stretch Read Delay in (in [7:0])
#define         REGMAX            0x1C  // Size of register array

// General purpose offsets for High and Low parts of 16-bit registers
#define         REGH              0
#define         REGL              1

// The I2C register array
volatile uint8_t m_anRegArray[REGMAX];

// I2C register address currently being accessed
volatile uint8_t  m_nRegPos = 0;

// ========================

// --------------------------------------------------
// Configuration: GPS Decoding
// --------------------------------------------------

// The following defines the GPS RMC message that we will parse and extract
//
// Example (GPRMC):  "$GPRMC,194509.000,A,4042.6142,N,07400.4168,W,2.03,221.11,160412,,,A*77"
//         (GNRMC):  "$GNRMC,023123.20,A,3725.54083,N,12205.08740,W,0.064,,240516,,,D*73"
//
// Uncomment one of the two following lines, depending on which RMC message
// is expected from the GPS unit.
//
//char m_acGpsFmt[]="$GPRMC,dddtdd.ddm,A,eeae.eeee,l,eeeae.eeee,o,djdk,ddd.dc,dddy??,,,?z#x";
  char m_acGpsFmt[]="$GNRMC,dddtdd.dm,A,eeae.eeeee,l,eeeae.eeeee,o,djddk,ddd.dc,dddy??,,,?z#x";

                   
// GPS temp variables
volatile uint8_t        m_nGpsState = 0;
unsigned int            m_nTemp;
long                    m_nLTemp;

// GPS checksum calculation
volatile boolean    m_bGpsChkEn = 0;
volatile uint8_t    m_nGpsChkSum = 0;
volatile uint8_t    m_nGpsChkVal = 0;

// GPS output variables
volatile unsigned int   m_nGpsTime, m_nGpsMsecs, m_nGpsKnots, m_nGpsCourse, m_nGpsDate;
volatile long           m_nGpsLat, m_nGpsLong;
volatile boolean        m_bGpsFix = 0;
volatile bool           m_bGpsValid = 0;
volatile boolean        m_bGpsChkOk = 0;


// --------------------------------------------------
// Configuration: LED Blinking
// --------------------------------------------------

// Define delays for the Blink FSM
#define BLINK_FLASH_ON  20    // Time for LED to be on during blink (in ms)
#define BLINK_FLASH_OFF 150   // Time for LED to be off between blinks (in ms)

// States for the Blink FSM
enum teBlinkFsm {E_BLFSM_IDLE,E_BLFSM_ON,E_BLFSM_OFF};

// Temporary variables for the Blink FSM
volatile uint8_t  nBlinkStatus = 0;
volatile uint8_t  nBlinkRemain = 0;
volatile uint8_t  eBlinkFsmState = E_BLFSM_IDLE;
volatile uint16_t nBlinkFsmTimeMsNxt = 0;


// --------------------------------------------------
// Interrupt Service Routines
// --------------------------------------------------

// ISR for UART error
// - This is used to recover from overrun / framing errors
ISR(LIN_ERR_vect) {
  // Check for UART overflow or framing errors
  if (LINERR & ((1<<LOVERR) | (1<<LFERR)) ) {
    // Clear status by reading LINDAT or setting LINSIR:LERR
    LINSIR |= (1<<LERR);
  }
}

// Called by I2C ISR on read data transactions
// - Referenced in USI_OVERFLOW_VECTOR in usiTwiSlave via TinyWireS wrapper
void requestEvent() {

  // Send back read data (into Tx buffer)
  TinyWireS.send(m_anRegArray[m_nRegPos]);
  
  // Increment read address (with wrap)
  m_nRegPos = ((m_nRegPos+1) >= REGMAX)? 0x00 : m_nRegPos+1;

#ifdef I2C_RD_STRETCH_EN
  // Raspberry Pi Clock Stretching Bug Workaround
  // - RPi does not properly handle certain clock stretch scenarios
  // - We can avoid the majority of issues by ensuring that the
  //   clock stretch between the READ-ACK and DATA is greater than
  //   0.5 I2C clock periods.
  // - For a 62.5kHz I2C clock (RPi 3 default), this is 8.0us.
  // - For an ATtiny with internal clock of 16MHz, this is 128 cycles.
  // - Accounting for the overhead in the USI Overflow ISR response
  //   latency and "usiTwiSlave" overhead, this only required an
  //   small delay loop here.
  // - Provide a register-controlled clock stretch delay so that
  //   the I2C master can tune the performance as needed.
  // - I2C master can configure as follows:
  //     I2C WRITE (device=0x28) (addr=0x1B) (delay=0x08 - change this value)
  //
  uint8_t     nDelayInd=0;
  uint8_t     nDelayMax = m_anRegArray[REG_I2C_CTRL+REGL];
  for (nDelayInd=0;nDelayInd<nDelayMax;nDelayInd++) {
      NOP;
      NOP;
  }
#endif  
  
}

// Called by I2C ISR on write data transactions or address setup transactions
// - Referenced in USI_OVERFLOW_VECTOR in usiTwiSlave via TinyWireS wrapper
void receiveEvent(uint8_t nLen) {
  if (nLen == 0) {
    // ERROR
    return;
  }
  if (nLen > 16) {
    // ERROR: Too large
    // TODO: Decide on appropriate size
    return;
  }
  
  // Update current register position with first byte in transaction
  m_nRegPos = TinyWireS.receive();

  // Perform address bounds check, ignore write if invalid
  if (m_nRegPos >= REGMAX) {
    return;
  }

  // Now that we consumed the address byte, are there any
  // further bytes to process (for register write)?
  nLen--;
  if (nLen == 0) {
    // No more bytes to process, therefore we were just
    // setting up the register address
    return;
  }

  // More bytes to process, so store into registers
  uint8_t nWrVal;
  while (nLen--) {
    // Perform register write with next incoming byte
    nWrVal = TinyWireS.receive();
    
    // Handle special-case registers
    // - For example, any registers that have write-1-to-clear
    //   functionality (or read-to-clear).
    if (m_nRegPos == REG_STATUS+REGH) {
      if (nWrVal & 0x80) {
        // Clear GPS valid flag, which allows the next GPS fix
        // to be saved into the registers
        m_bGpsValid = 0;

        // Revise register immediately to show it as cleared instead
        // of waiting for next UART receive byte to update status reg
        uint8_t nRegValTmp = m_anRegArray[REG_STATUS+REGH];
        nRegValTmp &= ~0x80;
        m_anRegArray[REG_STATUS+REGH] = nRegValTmp;
      }
    } else {
      // General register
      // TODO: Can make registers read-only here if preferred
      m_anRegArray[m_nRegPos] = nWrVal;

    } // m_nRegPos
    
    // Increment read address (with wrap)
    m_nRegPos = ((m_nRegPos+1) >= REGMAX)? 0x00 : m_nRegPos+1;
  }
  
}

// --------------------------------------------------
// Functions: GPS
// --------------------------------------------------

// GPS decoding
// - Based on "Minimal GPS Parser" code by David Johnson-Davies
//   http://www.technoblogy.com/show?SJ0
// - Modifications by Calvin Hass ( http://www.impulseadventure.com/elec/ ):
// - - Added checksum calculation
// - - Changed format to support $GNRMC as well as $GPRMC
// - - Support skipping of "course over ground" field
// - - Moved GpsFix flag into checksum field
// - - Corrected some initialization and resets
// - - Renamed variable names

void ParseGPS (char nCh) {
  
  if (nCh == '$') {  
    m_nGpsState = 0;
    m_nTemp = 0;
    m_nLTemp = 0;
    // Start checksum accumulation disabled
    m_bGpsChkEn = 0;
    m_bGpsChkOk = 0; 
  }

  // Handle any fields that might be skipped in output
  if ((m_nGpsState == 52) && (nCh == ',')) { m_nGpsState += 6; }  // Skip COURSE

  char cMode = m_acGpsFmt[m_nGpsState++];
  // d=decimal digit
  char nDigit = nCh - '0';
  // #=hexadecimal (uppercase) digit
  char nHex = (nCh>='A')?(nCh-'A'+10):(nCh-'0');
  
  if (cMode == 'd') m_nTemp = m_nTemp*10 + nDigit;
  // #=hex digit
  else if (cMode == '#') m_nTemp = m_nTemp*16 + nHex;
  // e=long decimal digit
  else if (cMode == 'e') m_nLTemp = m_nLTemp*10 + nDigit;
  // a=angular measure
  else if (cMode == 'a') m_nLTemp = m_nLTemp*6 + nDigit;
  // t=Time - hhmm
  else if (cMode == 't') { m_nGpsTime = m_nTemp*10 + nDigit; m_nTemp = 0; }
  // m=Millisecs
  else if (cMode == 'm') { m_nGpsMsecs = m_nTemp*10 + nDigit; m_nTemp=0; }
  // l=Latitude - in minutes*10000
  else if (cMode == 'l') { if (nCh == 'N') m_nGpsLat = m_nLTemp; else m_nGpsLat = -m_nLTemp; m_nLTemp = 0; }
  // o=Longitude - in minutes*10000
  else if (cMode == 'o') { if (nCh == 'E') m_nGpsLong = m_nLTemp; else m_nGpsLong = -m_nLTemp; m_nLTemp = 0; }
  // j/k=Speed - in knots*100
  else if (cMode == 'j') { if (nCh != '.') { m_nTemp = m_nTemp*10 + nDigit; m_nGpsState--; } }
  else if (cMode == 'k') { m_nGpsKnots = m_nTemp*10 + nDigit; m_nTemp = 0; }
  // c=Course (Track) - in degrees*100
  else if (cMode == 'c') { m_nGpsCourse = m_nTemp*10 + nDigit; m_nTemp = 0; }
  // y=Date - ddmm
  else if (cMode == 'y') { m_nGpsDate = m_nTemp*10 + nDigit ; m_nTemp = 0; }

  // z=End of checksum range
  else if (cMode == 'z') {

    if (nCh == '*') {
      // Turn off checksum accumulation. Later, we will
      // compare the check value against this accumulation.
      m_bGpsChkEn = 0;

    } else {
      // Didn't get checksum marker, so abort
      m_nGpsState = 0;
    }
  }
  // x=End of checksum value
  else if (cMode == 'x') {
    // Capture the checksum comparison value
    m_nGpsChkVal = m_nTemp*16 + nHex;
    m_bGpsChkOk = (m_nGpsChkVal == m_nGpsChkSum);
    // Signal that this is the last field in the message
    m_bGpsFix = 1;

    // Reset the state vector so that we don't continue further
    m_nGpsState = 0;
  }
  
  // If received character matches format string, or format is '?' - return
  else if ((cMode == nCh) || (cMode == '?')) {
    // Valid character, but no special processing required
  }
  //  Unexpected character; abort
  else m_nGpsState = 0;

  // If checksum accumulation enabled, add current byte
  if (m_bGpsChkEn) {
    m_nGpsChkSum ^= nCh;
  }

  // If we just finished processing "$" then turn on checksum
  // accumulation for following bytes. Note that we do
  // this after we have performed the checksum accumulation
  // step above. This simplifies the state machine.
  if (cMode == '$') {
    m_bGpsChkEn = 1;
    // After the "$" we can start checksum accumulation
    m_nGpsChkSum = 0;
  }

}


// --------------------------------------------------
// Program Initialization
// --------------------------------------------------

void setup() {
 
  // Initialize register array
  for (int nRegInd=0;nRegInd<REGMAX;nRegInd++) {
    m_anRegArray[nRegInd] = 0x00;
  }
  // Special overrides
  m_anRegArray[REG_FIXED+REGH] = 0x12;
  m_anRegArray[REG_FIXED+REGL] = 0x34;

  // Set default clock stretching
  m_anRegArray[REG_I2C_CTRL+REGL] = I2C_RD_STRETCH_DEFAULT;

  // Configure GPIO pins
  SETPIN1_LED_L();
  SETPIN1_LED_DIR_O();

  // Configure the I2C interface
  TinyWireS.begin(I2C_SLAVE_ADDRESS);
  TinyWireS.onReceive(receiveEvent);
  TinyWireS.onRequest(requestEvent);


  // Configure the UART interface
  Serial.begin(UART_RATE);

  // Enable UART Error interrupt
  LINENIR |= (1<<LENERR);

  // Enable interrupts
  sei();  

  // Boot complete indicator
  // - Indicate that bootloader is complete and we
  //  are ready to accept I2C transactions
#ifdef LED_FLASH_EN  
  doBlinkFlash(4);
#endif

}


// --------------------------------------------------
// Program Main Loop
// --------------------------------------------------

void loop() {
  uint16_t    nTimeMsCur;
  uint16_t    nTimeMsElapsed;
  int         nUartIn;

  // To help debugging, always reset the FIXED register
  // To debug bad writes, use a different R/W register
  m_anRegArray[REG_FIXED +REGH] = 0x12;
  m_anRegArray[REG_FIXED +REGL] = 0x34;

  // Determine if there is an incoming UART RX byte ready to be processed
  if (Serial.available()) {
    nUartIn = Serial.read();
    if (nUartIn != -1) {
      
      // Process the UART RX byte (nUartIn)

      // Reset the fix indicator
      // - This lets us detect if we have just completed the last byte
      //   of the NMEA string. This is used to ensure we have coherency
      //   with all the GPS variables when we copy them to the SPI regs.
      m_bGpsFix = 0;
      
      // Feed GPS data directly into parser
      // - This will update the m_nGps* variables
      // - On the last byte of the special NMEA sentence, we will set m_bGpsFix=1
      ParseGPS(nUartIn);

      // If we just completed parsing the special message, we expect the following
      // variables have been assigned:
      // - m_bGpsFix   = 1
      // - m_bGpsChkOk = 0/1

        
      // Only latch the new values into reg array when we have just completed updating all GPS variables
      if (m_bGpsFix) {
#ifdef LED_FLASH_GPS_EN        
        SETPIN1_LED_H();
#endif        

        // Only update the I2C reg array when the regs are marked as ready for
        // new data (ie. invalid).
        if (m_bGpsValid == 0) {
          
          // Copy over all the GPS variables into the I2C reg array
          // This is coherent because we know we've finished an NMEA message
          // by the fact that m_bGpsFix=1 in this cycle.
           
          m_anRegArray[REG_GPS_TIME  +REGH] = (m_nGpsTime   & 0xFF00) >> 8;
          m_anRegArray[REG_GPS_TIME  +REGL] = (m_nGpsTime   & 0x00FF);
          m_anRegArray[REG_GPS_MSECS +REGH] = (m_nGpsMsecs  & 0xFF00) >> 8;
          m_anRegArray[REG_GPS_MSECS +REGL] = (m_nGpsMsecs  & 0x00FF);
          m_anRegArray[REG_GPS_KNOTS +REGH] = (m_nGpsKnots  & 0xFF00) >> 8;
          m_anRegArray[REG_GPS_KNOTS +REGL] = (m_nGpsKnots  & 0x00FF);
          m_anRegArray[REG_GPS_COURSE+REGH] = (m_nGpsCourse & 0xFF00) >> 8;
          m_anRegArray[REG_GPS_COURSE+REGL] = (m_nGpsCourse & 0x00FF);
          m_anRegArray[REG_GPS_DATE  +REGH] = (m_nGpsDate   & 0xFF00) >> 8;
          m_anRegArray[REG_GPS_DATE  +REGL] = (m_nGpsDate   & 0x00FF);
          m_anRegArray[REG_GPS_LAT_H +REGH] = (m_nGpsLat    & 0xFF000000)>>24;
          m_anRegArray[REG_GPS_LAT_H +REGL] = (m_nGpsLat    & 0x00FF0000)>>16;
          m_anRegArray[REG_GPS_LAT_L +REGH] = (m_nGpsLat    & 0x0000FF00)>>8;
          m_anRegArray[REG_GPS_LAT_L +REGL] = (m_nGpsLat    & 0x000000FF)>>0;
          m_anRegArray[REG_GPS_LONG_H+REGH] = (m_nGpsLong   & 0xFF000000)>>24;
          m_anRegArray[REG_GPS_LONG_H+REGL] = (m_nGpsLong   & 0x00FF0000)>>16;
          m_anRegArray[REG_GPS_LONG_L+REGH] = (m_nGpsLong   & 0x0000FF00)>>8;
          m_anRegArray[REG_GPS_LONG_L+REGL] = (m_nGpsLong   & 0x000000FF)>>0;      

          // For debug purposes, provide expected and actual checksum values
          m_anRegArray[REG_GPS_CHECK +REGH] = (m_nGpsChkVal & 0x00FF);
          m_anRegArray[REG_GPS_CHECK +REGL] = (m_nGpsChkSum & 0x00FF);

          // Indicate SPI register contains new value
          m_bGpsValid = true;

          // Update status register
          uint8_t nNewStatus = 0x00;
          nNewStatus |= (m_bGpsValid)?0x80:0x00;
          nNewStatus |= (m_bGpsFix)  ?0x40:0x00;
          nNewStatus |= (m_bGpsChkOk)?0x20:0x00;
          m_anRegArray[REG_STATUS    +REGH] = nNewStatus;           
          //m_anRegArray[REG_STATUS    +REGL] = 0x00;
          
        } // m_bGpsValid
      } // m_bGpsFix
     
    } // nUartIn
  } // Serial.available()


  // Turn off the LED
#ifdef LED_FLASH_GPS_EN  
  SETPIN1_LED_L();  
#endif  


  // Check for stop conditions on I2C
  // - Unfortunately the I2C stop condition is not handled
  //   directly by ATtiny hardware ISR so we must resort to polling
  // - This routine in TinyWireS checks the USI Stop Condition Flag (USIPF)
  //   which is set when a stop condition has been detected
  // - If USIPF is set, then the onRequest() handler is called with
  //   any data that is in the usiTwiSlave receive buffer. 
  TinyWireS_stop_check();


  // Periodically flash the link status
#ifdef LED_FLASH_EN 
  setBlinkState(1); 
  doBlinkFsm();
#endif

}

// =================================



// Define the number of blinks to periodically flash
void setBlinkState(int nNum) {
  nBlinkStatus = nNum;
}

// Update the LED status based on the blink FSM
// - This blink routine is designed to use non-blocking
//   waits, so it does not waste CPU resources. It
//   relies upon defining timer thresholds for each
//   stage of the blink request.
void doBlinkFsm() {
  uint16_t nTimeMsCur = millis();
  // Calculate next state
  switch (eBlinkFsmState) {
    case E_BLFSM_IDLE:
      SETPIN1_LED_L();     
      if (nTimeMsCur > nBlinkFsmTimeMsNxt) {
        if (nBlinkStatus > 0) {
          eBlinkFsmState = E_BLFSM_ON;
          nBlinkFsmTimeMsNxt = nTimeMsCur + BLINK_FLASH_ON;
          nBlinkRemain = nBlinkStatus;
        }
      }
      break;
    case E_BLFSM_ON:
      SETPIN1_LED_H();     
      if (nTimeMsCur > nBlinkFsmTimeMsNxt) {
        if (nBlinkRemain==0) {
          // Should never get here
          eBlinkFsmState = E_BLFSM_IDLE;
          nBlinkFsmTimeMsNxt = nTimeMsCur + LED_FLASH_PERIOD;     
        } else {
          nBlinkRemain--;
          eBlinkFsmState = E_BLFSM_OFF;
          nBlinkFsmTimeMsNxt = nTimeMsCur + BLINK_FLASH_OFF;          
        }
      }
      break;
    case E_BLFSM_OFF:
      SETPIN1_LED_L();     
      if (nTimeMsCur > nBlinkFsmTimeMsNxt) {
        if (nBlinkRemain==0) {
          eBlinkFsmState = E_BLFSM_IDLE;
          nBlinkFsmTimeMsNxt = nTimeMsCur + LED_FLASH_PERIOD;
        } else {
          eBlinkFsmState = E_BLFSM_ON;
          nBlinkFsmTimeMsNxt = nTimeMsCur + BLINK_FLASH_ON;
        }
      }
      break;
  }
}

// Flash the LED quickly [nNum] times
// - This routine uses blocking waits
void doBlinkFlash(int nNum) {
  SETPIN1_LED_DIR_O();
  for (int i=0;i<nNum;i++) {
    SETPIN1_LED_H();     
    delay(20);
    SETPIN1_LED_L();     
    delay(150);
  }
}


// =================================

					

 


Reader's Comments:

Please leave your comments or suggestions below!

 


Leave a comment or suggestion for this page:

(Never Shown - Optional)
 

Visits!