changeset 226:0639ad84827b main

Checkpoint merge
author Bob Cook <bob@bobcookdev.com>
date Sat, 28 Jun 2014 16:13:53 -0700
parents c4009187a522 (current diff) d1e6f4dad86a (diff)
children 03064d391e79
files main/packages/common/can/can_messages.h
diffstat 32 files changed, 3249 insertions(+), 95 deletions(-) [+]
line wrap: on
line diff
--- a/main/jrules/avr.common.inc	Sat Jun 28 16:12:28 2014 -0700
+++ b/main/jrules/avr.common.inc	Sat Jun 28 16:13:53 2014 -0700
@@ -112,6 +112,7 @@
         case atmega328p : AVR_DEVICE = atmega328p ;
         case atmega128  : AVR_DEVICE = atmega128 ;
         case atmega16m1 : AVR_DEVICE = atmega16m1 ;
+        case atmega64m1 : AVR_DEVICE = atmega64m1 ;
         case *          : exit "*** avr_executable: unsupported device $(1[2])" ;
     }
     
--- a/main/packages/avr/can/m1.cpp	Sat Jun 28 16:12:28 2014 -0700
+++ b/main/packages/avr/can/m1.cpp	Sat Jun 28 16:13:53 2014 -0700
@@ -7,7 +7,7 @@
 //    
 //  This file implements CAN support for the mega(16,32,64)m1 chip.
 //
-//  Copyright (C) 2012 Bob Cook
+//  Copyright (C) 2012-2014 Bob Cook
 //
 //  Permission is hereby granted, free of charge, to any person obtaining a copy
 //  of this software and associated documentation files (the "Software"), to deal
@@ -221,22 +221,7 @@
 
 #if ( PRJ_CPU_FREQ == 16000000 )
 
-#if defined( PRJ_M1CAN_CANBUS_20_KHZ )
-
-    CANBT1 = ( 0 << BRP5 ) | ( 1 << BRP4 ) | ( 0 << BRP3 )      // BRP = 19 + 1
-           | ( 0 << BRP2 ) | ( 1 << BRP1 ) | ( 1 << BRP0 )
-           ;
-
-    CANBT2 = ( 0 << SJW1 ) | ( 0 << SJW0 )                      // SWJ = 0
-           | ( 0 << PRS2 ) | ( 1 << PRS1 ) | ( 0 << PRS0 )      // PRS = 2 + 1
-           ;
-
-    CANBT3 = ( 1 << PHS22 ) | ( 1 << PHS21 ) | ( 1 << PHS20 )   // PHSEG2 = 7 + 1
-           | ( 1 << PHS12 ) | ( 1 << PHS11 ) | ( 1 << PHS10 )   // PHSEG1 = 7 + 1
-           | ( 0 << SMP )                                       // SMP = 0
-           ;
-
-#elif defined( PRJ_M1CAN_CANBUS_125_KHZ )
+#if defined( PRJ_M1CAN_CANBUS_125_KHZ )
 
     CANBT1 = ( 0 << BRP5 ) | ( 0 << BRP4 ) | ( 0 << BRP3 )      // BRP = 7 + 1
            | ( 1 << BRP2 ) | ( 1 << BRP1 ) | ( 1 << BRP0 )
@@ -367,6 +352,7 @@
 
         if ( ! g_tx_buffer.is_empty() )
         {
+            spinwait_delay_ms( 1 );
             send_from_tx_buffer();
         }
         else
@@ -374,7 +360,8 @@
             CANCDMOB &= ~( ( 1 << CONMOB1 ) | ( 1 << CONMOB0 ) );
         }
     }
-    else if ( CANSTMOB & ( 1 << RXOK ) )
+    
+    if ( CANSTMOB & ( 1 << RXOK ) )
     {
         read_into_rx_buffer();
         CANSTMOB &= ~( 1 << RXOK ); // clear interrupt reason
@@ -468,7 +455,25 @@
     
 bool m1can_request( const uint32_t& can_id )
 {
-    //--    Got space in the buffer?
+    //--    Disable the CAN interrupt to avoid specific race conditions.
+
+    m1_guard_can_int_disable can_int_disabled;    
+
+    //--    We might be able to send this request immediately, if the TX buffer is empty
+    //      and there is not a transmit already in progress.
+
+    if ( g_tx_buffer.is_empty() )
+    {
+        CANPAGE = 0;
+
+        if ( ( CANCDMOB & ( ( 1 << CONMOB1 ) | ( 1 << CONMOB0 ) ) ) == 0 )
+        {
+            send_request( can_id );
+            return true;
+        }
+    }
+
+    //--    Deferred transmit. Got space in the buffer?
 
     if ( g_tx_buffer.is_full() )
     {
@@ -485,13 +490,6 @@
     
     g_tx_buffer.push( msg );
 
-    //--    And send it now, if the TX process is idle.
-    
-    if ( ( CANCDMOB & ( ( 1 << CONMOB1 ) | ( 1 << CONMOB0 ) ) ) == 0 )
-    {
-        send_from_tx_buffer();
-    }
-
     return true;
 }
 
--- a/main/packages/avr/can/mcp2515.cpp	Sat Jun 28 16:12:28 2014 -0700
+++ b/main/packages/avr/can/mcp2515.cpp	Sat Jun 28 16:13:53 2014 -0700
@@ -435,6 +435,7 @@
 static void send_from_tx_buffer()
 {
     mcp2515_data_t  msg = g_tx_buffer.pop();
+return; // TODO remove this
 
     //--    Load the destination address.
 
@@ -767,6 +768,31 @@
             send_from_tx_buffer();
         }
     }
+    
+    if ( canintf & MCP_INTERRUPT_MERR )
+    {
+        modify_register( MCP_REGISTER_CANINTF, MCP_INTERRUPT_MERR, 0x00 );
+    }
+
+    if ( canintf & MCP_INTERRUPT_WAKI )
+    {
+        modify_register( MCP_REGISTER_CANINTF, MCP_INTERRUPT_WAKI, 0x00 );
+    }
+
+    if ( canintf & MCP_INTERRUPT_ERRI )
+    {
+        modify_register( MCP_REGISTER_CANINTF, MCP_INTERRUPT_ERRI, 0x00 );
+    }
+
+    if ( canintf & MCP_INTERRUPT_TX2I )
+    {
+        modify_register( MCP_REGISTER_CANINTF, MCP_INTERRUPT_TX2I, 0x00 );
+    }
+
+    if ( canintf & MCP_INTERRUPT_TX1I )
+    {
+        modify_register( MCP_REGISTER_CANINTF, MCP_INTERRUPT_TX1I, 0x00 );
+    }
 }
 
 #endif
@@ -880,7 +906,7 @@
         uint8_t         priority
     )
 {
-    
+
 #if defined( PRJ_MCP2515_USE_INTERRUPT_RXTX )
 
     //--    Got space in the buffer?
--- a/main/packages/avr/device/adc.cpp	Sat Jun 28 16:12:28 2014 -0700
+++ b/main/packages/avr/device/adc.cpp	Sat Jun 28 16:13:53 2014 -0700
@@ -7,7 +7,7 @@
 //    
 //  This file provides a basic Analog to Digitial Conversion (ADC) library.
 //
-//  Copyright (C) 2007 Bob Cook
+//  Copyright (C) 2007-2014 Bob Cook
 //
 //  Permission is hereby granted, free of charge, to any person obtaining a copy
 //  of this software and associated documentation files (the "Software"), to deal
--- a/main/packages/avr/device/hwdefs.h	Sat Jun 28 16:12:28 2014 -0700
+++ b/main/packages/avr/device/hwdefs.h	Sat Jun 28 16:13:53 2014 -0700
@@ -7,7 +7,7 @@
 //    
 //  This file provides abstracted definitions for the underlying AVR hardware.
 //
-//  Copyright (C) 2010 Bob Cook
+//  Copyright (C) 2010-2014 Bob Cook
 //
 //  Permission is hereby granted, free of charge, to any person obtaining a copy
 //  of this software and associated documentation files (the "Software"), to deal
@@ -44,7 +44,8 @@
     && !defined( __AVR_ATmega168__ ) \
     && !defined( __AVR_ATmega328P__ ) \
     && !defined( __AVR_ATmega128__ ) \
-    && !defined( __AVR_ATmega16M1__ )
+    && !defined( __AVR_ATmega16M1__ ) \
+    && !defined( __AVR_ATmega64M1__ )
 #error Unsupported Hardware
 #endif
 
@@ -146,6 +147,16 @@
 #define     HW_FLASH_SIZE               (16 * 1024)
 #define     HW_EEPROM_SIZE              512
 
+#elif defined( __AVR_ATmega64M1__ )
+
+#define     HW_HAS_PORTB
+#define     HW_HAS_PORTC
+#define     HW_HAS_PORTD
+
+#define     HW_RAM_SIZE                 (4 * 1024)
+#define     HW_FLASH_SIZE               (64 * 1024)
+#define     HW_EEPROM_SIZE              (2 * 1024)
+
 #endif
 
 // ----------------------------------------------------------------------------------------
@@ -223,7 +234,7 @@
 #define     HW_SPI_MOSI_PIN             PB2
 #define     HW_SPI_SS_PIN               PB0
 
-#elif defined( __AVR_ATmega16M1__ )
+#elif defined( __AVR_ATmega16M1__ ) || defined( __AVR_ATmega64M1__ )
 
 #define     HW_HAS_SPI
 
@@ -315,7 +326,7 @@
 #define     HW_UART1_RX_PIN             PD2
 #define     HW_UART1_TX_PIN             PD3
 
-#elif defined( __AVR_ATmega16M1__ )
+#elif defined( __AVR_ATmega16M1__ ) || defined( __AVR_ATmega64M1__ )
 
 #define     HW_HAS_UART0
 
@@ -392,7 +403,7 @@
 #define     HW_TWI_SDA_PIN              PD1
 #define     HW_TWI_SCL_PIN              PD0
 
-#elif defined( __AVR_ATmega16M1__ )
+#elif defined( __AVR_ATmega16M1__ ) || defined( __AVR_ATmega64M1__ )
 
 // no TWI h/w for this chip
 
@@ -485,7 +496,7 @@
 
 #define     HW_ADC_REG_ADMUX            ADMUX
 
-#elif defined( __AVR_ATmega16M1__ )
+#elif defined( __AVR_ATmega16M1__ ) || defined( __AVR_ATmega64M1__ )
 
 #define     HW_HAS_ADC
 // this chip has an odd arrangement of ADC pins; see datasheet
--- a/main/packages/avr/device/int_helpers.cpp	Sat Jun 28 16:12:28 2014 -0700
+++ b/main/packages/avr/device/int_helpers.cpp	Sat Jun 28 16:13:53 2014 -0700
@@ -7,7 +7,7 @@
 //    
 //  This file provides support routines for dealing with interrupts.
 //
-//  Copyright (C) 2008 Bob Cook
+//  Copyright (C) 2008-2014 Bob Cook
 //
 //  Permission is hereby granted, free of charge, to any person obtaining a copy
 //  of this software and associated documentation files (the "Software"), to deal
@@ -68,7 +68,7 @@
     TIMSK  = 0;
     ETIMSK = 0;
 
-#elif defined( __AVR_ATmega16M1__ )
+#elif defined( __AVR_ATmega16M1__ ) || defined( __AVR_ATmega64M1__ )
 
     TIMSK0 = 0;
     TIMSK1 = 0;
--- a/main/packages/avr/device/simavr_helpers.inl	Sat Jun 28 16:12:28 2014 -0700
+++ b/main/packages/avr/device/simavr_helpers.inl	Sat Jun 28 16:13:53 2014 -0700
@@ -7,7 +7,7 @@
 //    
 //  This file provides support routines for working with the "simavr" simulator.
 //
-//  Copyright (C) 2011 Bob Cook
+//  Copyright (C) 2011-2014 Bob Cook
 //
 //  Permission is hereby granted, free of charge, to any person obtaining a copy
 //  of this software and associated documentation files (the "Software"), to deal
@@ -50,6 +50,8 @@
 #define SIMAVR_PROCESSOR_NAME "atmega128"
 #elif defined( __AVR_ATmega16M1__ )
 #define SIMAVR_PROCESSOR_NAME "atmega16m1"
+#elif defined( __AVR_ATmega64M1__ )
+#define SIMAVR_PROCESSOR_NAME "atmega64m1"
 #error Unsupported Hardware
 #endif
 
--- a/main/packages/avr/device/uart.cpp	Sat Jun 28 16:12:28 2014 -0700
+++ b/main/packages/avr/device/uart.cpp	Sat Jun 28 16:13:53 2014 -0700
@@ -7,7 +7,7 @@
 //    
 //  This file provides the implementation for the UART library.
 //
-//  Copyright (C) 2010 Bob Cook
+//  Copyright (C) 2010-2014 Bob Cook
 //
 //  Permission is hereby granted, free of charge, to any person obtaining a copy
 //  of this software and associated documentation files (the "Software"), to deal
@@ -34,6 +34,7 @@
 #include <avr/interrupt.h>
 
 #include "hwdefs.h"
+#include "int_helpers.h"
 #include "project_defs.h"
 
 #include "packages/common/util/cbuffer.h"
@@ -101,14 +102,11 @@
     && !defined( __AVR_ATmega168__ ) \
     && !defined( __AVR_ATmega328P__ ) \
     && !defined( __AVR_ATmega128__ ) \
-    && !defined( __AVR_ATmega16M1__ )
+    && !defined( __AVR_ATmega16M1__ ) \
+    && !defined( __AVR_ATmega64M1__ )
 #error Unsupported Hardware
 #endif
 
-#if defined( PRJ_UART0_USE_INTERRUPT_MODE ) && defined( __AVR_ATmega16M1__ )
-#error UART0 interrupt mode not supported for ATmega16M1!
-#endif
-
 // ----------------------------------------------------------------------------------------
 
 #if defined( PRJ_UART0_USE_INTERRUPT_MODE ) || defined( PRJ_UART0_USE_POLLED_MODE )
@@ -124,7 +122,7 @@
 
 // ----------------------------------------------------------------------------------------
 
-#if defined( PRJ_UART0_USE_INTERRUPT_MODE )
+#if defined( PRJ_UART0_USE_INTERRUPT_MODE ) && !defined( __AVR_ATmega16M1__ ) && !defined( __AVR_ATmega64M1__ )
 
 #if defined( __AVR_ATmega8__ ) || defined( __AVR_ATmega16__ )
 
@@ -154,6 +152,10 @@
 
     uint8_t data = UDR0;
 
+#elif defined( __AVR_ATmega16M1__ ) || defined( __AVR_ATmega64M1__ )
+
+    uint8_t data = LINDAT;
+
 #endif
 
     // TODO: error checking in the USRC register, after reading UDR
@@ -174,7 +176,7 @@
 
 // ----------------------------------------------------------------------------------------
 
-#if defined( PRJ_UART0_USE_INTERRUPT_MODE )
+#if defined( PRJ_UART0_USE_INTERRUPT_MODE ) && !defined( __AVR_ATmega16M1__ ) && !defined( __AVR_ATmega64M1__ )
 
 #if defined( __AVR_ATmega8__ ) || defined( __AVR_ATmega16__ )
 
@@ -233,6 +235,43 @@
 
 // ----------------------------------------------------------------------------------------
 
+#if defined( PRJ_UART0_USE_INTERRUPT_MODE ) && ( defined( __AVR_ATmega16M1__ ) || defined( __AVR_ATmega64M1__ ) )
+
+ISR( LIN_TC_vect )
+{
+PORTB |= ( 1 << PB2 );
+
+    if ( LINSIR & ( 1 << LRXOK ) )
+    {
+        if ( uart0_rx_buffer.is_full() )
+        {
+            // overflow error
+
+            // TODO
+        }
+        else
+        {
+            uart0_rx_buffer.push( static_cast< uint8_t >( LINDAT ) );
+        }
+    }
+
+    if ( LINSIR & ( 1 << LTXOK ) )
+    {
+        if ( ! uart0_tx_buffer.is_empty() )
+        {
+            LINDAT = uart0_tx_buffer.pop();
+        }
+        else
+        {
+            LINENIR &= ~( 1 << LENTXOK ); // tx buffer empty, disable the interrupt
+        }
+    }
+}
+
+#endif
+
+// ----------------------------------------------------------------------------------------
+
 void uart0_init()
 {
 
@@ -356,7 +395,7 @@
         
     //-------------------------------------------------------------------------------------
 
-#elif defined( __AVR_ATmega16M1__ )
+#elif defined( __AVR_ATmega16M1__ ) || defined( __AVR_ATmega64M1__ )
 
     // reset the LIN/UART module
 
@@ -374,6 +413,24 @@
 
     LINCR |= ( 1 << LCMD1 ) | ( 1 << LCMD0 );   // tx & rx output enable
 
+    LINENIR = 0
+
+#if defined( PRJ_UART0_USE_INTERRUPT_MODE )
+
+           | ( 0 << LENERR )                    // no error handler interrupts
+           | ( 0 << LENTXOK )                   // no transmit complete interrupts
+           | ( 1 << LENRXOK )                   // enable receive complete interrupts
+           ;
+
+#else // defined( PRJ_UART0_USE_POLLED_MODE )
+
+           | ( 0 << LENERR )                    // no interrupts
+           | ( 0 << LENTXOK )
+           | ( 0 << LENRXOK )
+           ;
+
+#endif
+
 #endif  // #if defined( ...processor selection... )
 
     //-------------------------------------------------------------------------------------
@@ -406,7 +463,7 @@
     UBRR0H = highbyte;  // the caller is required to mask off the top bits
     UBRR0L = lowbyte;
     
-#elif defined( __AVR_ATmega16M1__ )
+#elif defined( __AVR_ATmega16M1__ ) || defined( __AVR_ATmega64M1__ )
 
     // the original computation (for non-M1 devices) uses a divsor of 16 rather than 32,
     // so divide the input value by 2
@@ -441,7 +498,7 @@
 
     return ( UCSR0A & ( 1 << RXC0 ) ) != 0;
 
-#elif defined( __AVR_ATmega16M1__ )
+#elif defined( __AVR_ATmega16M1__ ) || defined( __AVR_ATmega64M1__ )
 
     return ( LINSIR & ( 1 << LRXOK ) ) != 0;
 
@@ -475,7 +532,7 @@
     while ( ! ( UCSR0A & ( 1 << RXC0 ) ) ) ;
     return UDR0;
 
-#elif defined( __AVR_ATmega16M1__ )
+#elif defined( __AVR_ATmega16M1__ ) || defined( __AVR_ATmega64M1__ )
 
     while ( ! ( LINSIR & ( 1 << LRXOK ) ) ) ;
     return LINDAT;
@@ -506,7 +563,7 @@
 void uart0_write( uint8_t data )
 {
 
-#if defined( PRJ_UART0_USE_INTERRUPT_MODE )
+#if defined( PRJ_UART0_USE_INTERRUPT_MODE ) && !defined( __AVR_ATmega16M1__ ) && !defined( __AVR_ATmega64M1__ )
 
     //--    Wait for space in the buffer.
 
@@ -525,7 +582,23 @@
 
     UCSR0B |= ( 1 << UDRIE0 );
 
-#endif
+#endif // non-16M1 transmission
+
+#elif defined( PRJ_UART0_USE_INTERRUPT_MODE ) && ( defined( __AVR_ATmega16M1__ ) || defined( __AVR_ATmega64M1__ ) )
+
+    //--    If transmission is already in progress, all we need to do is add the
+    //      new data to the TX buffer. Otherwise we have to write the data directly.
+
+    if ( LINSIR & ( ( 1 << LBUSY ) | ( 1 << LTXOK ) ) )
+    {
+        uart0_tx_buffer.wait_and_push( data );
+    }
+    else
+    {
+        LINENIR |= ( 1 << LENTXOK );
+        while ( ( LINSIR & ( 1 << LBUSY ) ) ) ;
+        LINDAT = data;
+    }
 
 #else   // defined( PRJ_UART0_USE_POLLED_MODE )
 
@@ -542,11 +615,11 @@
     while ( ! ( UCSR0A & ( 1 << UDRE0 ) ) ) ;
     UDR0 = data;
 
-#elif defined( __AVR_ATmega16M1__ )
+#elif defined( __AVR_ATmega16M1__ ) || defined( __AVR_ATmega64M1__ )
 
     while ( ( LINSIR & ( 1 << LBUSY ) ) ) ;
-    //while ( ! ( LINSIR & ( 1 << LTXOK ) ) ) ;
     LINDAT = data;
+    //while ( LINSIR & ( 1 << LTXOK ) ) ;
 
 #endif
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/packages/avr/lcd/ili9340/ili9340.cpp	Sat Jun 28 16:13:53 2014 -0700
@@ -0,0 +1,964 @@
+// ----------------------------------------------------------------------------------------
+//
+//  avr/lcd/ili9340/ili9340.cpp
+//    
+//  Bob Cook Development, Robotics Library
+//  http://www.bobcookdev.com/rl/
+//    
+//  This file implements support for the ILI9340 240x320 RGB LCD driver.
+//
+//  Copyright (C) 2014 Bob Cook
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//  
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+//  
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+//
+//  Portions copied from "Adafruit_ILI9340" written by Limor Fried, Adafruit Industries.
+//  See https://github.com/adafruit/Adafruit_ILI9340
+//
+//  Portions copied from "Nokia 6100 LCD Display Driver" written by James P. Lynch.
+//  See http://www.sparkfun.com/tutorial/Nokia%206100%20LCD%20Display%20Driver.pdf
+//
+// ----------------------------------------------------------------------------------------
+
+#include "ili9340.h"
+
+#include <avr/io.h>
+
+#include "packages/avr/device/spi.h"
+#include "packages/avr/device/spinwait.h"
+
+#include "packages/common/font/font_defs.h"
+
+#include "project_defs.h"
+
+// always after other includes
+#include "packages/avr/device/workaround34734.h"
+
+
+#if defined( PRJ_ILI9340_ENABLE )
+
+// ----------------------------------------------------------------------------------------
+
+#if !defined( PRJ_SPI_ENABLE_MASTER )
+#error PRJ_SPI_ENABLE_MASTER is required for SPI communications!
+#endif
+
+#if !defined( PRJ_SPI_DATA_DIRECTION_MSB )
+#error PRJ_SPI_DATA_DIRECTION_MSB is required!
+#endif
+
+// ----------------------------------------------------------------------------------------
+
+#if !defined( PRJ_ILI9340_SELECT_PORT )
+#error PRJ_ILI9340_SELECT_PORT is required!
+#endif
+
+#if !defined( PRJ_ILI9340_SELECT_PIN )
+#error PRJ_ILI9340_SELECT_PIN is required!
+#endif
+
+#if !defined( PRJ_ILI9340_SELECT_DDR )
+#error PRJ_ILI9340_SELECT_DDR is required!
+#endif
+
+#if !defined( PRJ_ILI9340_COMMAND_PORT )
+#error PRJ_ILI9340_COMMAND_PORT is required!
+#endif
+
+#if !defined( PRJ_ILI9340_COMMAND_PIN )
+#error PRJ_ILI9340_COMMAND_PIN is required!
+#endif
+
+#if !defined( PRJ_ILI9340_COMMAND_DDR )
+#error PRJ_ILI9340_COMMAND_DDR is required!
+#endif
+
+#if defined( PRJ_ILI9340_USE_HW_RESET )
+
+#if !defined( PRJ_ILI9340_RESET_PORT )
+#error PRJ_ILI9340_RESET_PORT is required!
+#endif
+
+#if !defined( PRJ_ILI9340_RESET_PIN )
+#error PRJ_ILI9340_RESET_PIN is required!
+#endif
+
+#if !defined( PRJ_ILI9340_RESET_DDR )
+#error PRJ_ILI9340_RESET_DDR is required!
+#endif
+
+#endif // #if defined( PRJ_ILI9340_USE_HW_RESET )
+
+#if defined( PRJ_ILI9340_INC_WRITE_CSTRINGS ) || defined( PRJ_ILI9340_INC_WRITE_PGM_CSTRINGS )
+#if !defined( PRJ_ILI9340_INC_WRITE_CHAR )
+#define PRJ_ILI9340_INC_WRITE_CHAR
+#endif
+#endif
+
+// ----------------------------------------------------------------------------------------
+
+#if defined( PRJ_ILI9340_DISPLAY_ORIENTATION_0 ) \
+    || defined( PRJ_ILI9340_DISPLAY_ORIENTATION_180 )
+
+static const uint16_t ILI9340_MAX_HORIZONTAL_PIXELS = 240;
+static const uint16_t ILI9340_MAX_VERTICAL_PIXELS   = 320;
+
+#elif defined( PRJ_ILI9340_DISPLAY_ORIENTATION_90 ) \
+    || defined( PRJ_ILI9340_DISPLAY_ORIENTATION_270 )
+
+static const uint16_t ILI9340_MAX_HORIZONTAL_PIXELS = 320;
+static const uint16_t ILI9340_MAX_VERTICAL_PIXELS   = 240;
+
+#else
+#error PRJ_ILI9340_DISPLAY_ORIENTATION_* must be defined!
+#endif
+
+// ----------------------------------------------------------------------------------------
+
+const uint8_t ILI9340_COMMAND_SWRESET = 0x01;
+
+const uint8_t ILI9340_COMMAND_MADCTL = 0x36;
+const uint8_t ILI9340_MADCTL_MH      = ( 1 << 2 );
+const uint8_t ILI9340_MADCTL_BGR     = ( 1 << 3 );
+const uint8_t ILI9340_MADCTL_ML      = ( 1 << 4 );
+const uint8_t ILI9340_MADCTL_MV      = ( 1 << 5 );
+const uint8_t ILI9340_MADCTL_MX      = ( 1 << 6 );
+const uint8_t ILI9340_MADCTL_MY      = ( 1 << 7 );
+
+const uint8_t ILI9340_COMMAND_PWCTRL1  = 0xc0;
+const uint8_t ILI9340_PWCTRL1_VRH_MASK = 0x3f;
+const uint8_t ILI9340_PWCTRL1_VC_MASK  = 0x0f;
+
+const uint8_t ILI9340_COMMAND_PWCTRL2 = 0xc1;
+const uint8_t ILI9340_PWCTRL2_BT_MASK = 0x0f;
+
+const uint8_t ILI9340_COMMAND_VMCTR1  = 0xc5;
+const uint8_t ILI9340_VMCTR1_VMH_MASK = 0x7f;
+const uint8_t ILI9340_VMCTR1_VML_MASK = 0x7f;
+
+const uint8_t ILI9340_COMMAND_VMCTR2  = 0xc7;
+const uint8_t ILI9340_VMCTR2_nVM      = ( 1 << 7 );
+const uint8_t ILI9340_VMCTR2_VMF_MASK = 0x7f;
+
+const uint8_t ILI9340_COMMAND_PIXSET = 0x3a;
+const uint8_t ILI9340_PIXSET_16BPP   = 0x55;
+const uint8_t ILI9340_PIXSET_18BPP   = 0x66;
+
+const uint8_t ILI9340_COMMAND_FRMCTR1   = 0xb1;
+const uint8_t ILI9340_FRMCTR1_DIVA_MASK = 0x03;
+const uint8_t ILI9340_FRMCTR1_RTNA_MASK = 0x1f;
+
+const uint8_t ILI9340_COMMAND_DISCTRL    = 0xb6;
+const uint8_t ILI9340_DISCTRL_PTG_00     = ( 0x00 << 2 );
+const uint8_t ILI9340_DISCTRL_PTG_01     = ( 0x01 << 2 );
+const uint8_t ILI9340_DISCTRL_PTG_10     = ( 0x10 << 2 );
+const uint8_t ILI9340_DISCTRL_PTG_11     = ( 0x11 << 2 );
+const uint8_t ILI9340_DISCTRL_PT_00      = 0x00;
+const uint8_t ILI9340_DISCTRL_PT_01      = 0x01;
+const uint8_t ILI9340_DISCTRL_PT_10      = 0x10;
+const uint8_t ILI9340_DISCTRL_PT_11      = 0x11;
+const uint8_t ILI9340_DISCTRL_ISC_MASK   = 0x0f;
+const uint8_t ILI9340_DISCTRL_SM         = ( 1 << 4 );
+const uint8_t ILI9340_DISCTRL_SS         = ( 1 << 5 );
+const uint8_t ILI9340_DISCTRL_GS         = ( 1 << 6 );
+const uint8_t ILI9340_DISCTRL_REV        = ( 1 << 7 );
+const uint8_t ILI9340_DISCTRL_NL_MASK    = 0x1f;
+const uint8_t ILI9340_DISCTRL_PCDIV_MASK = 0x1f;
+
+const uint8_t ILI9340_COMMAND_SPLIN  = 0x10;
+const uint8_t ILI9340_COMMAND_SLPOUT = 0x11;
+
+const uint8_t ILI9340_COMMAND_DISPON = 0x29;
+
+const uint8_t ILI9340_COMMAND_CASET = 0x2a;
+const uint8_t ILI9340_COMMAND_PASET = 0x2b;
+const uint8_t ILI9340_COMMAND_RAMWR = 0x2c;
+
+// ----------------------------------------------------------------------------------------
+
+#if defined( PRJ_ILI9340_INC_WRITE_CHAR )
+
+const uint8_t ILI9340_MAX_HORIZONTAL_CHARS
+    = ( ILI9340_MAX_HORIZONTAL_PIXELS / FONT_8X12_GLYPH_WIDTH );
+
+const uint8_t ILI9340_MAX_VERTICAL_CHARS
+    = ( ILI9340_MAX_VERTICAL_PIXELS / FONT_8X12_GLYPH_HEIGHT );
+
+#endif // #if defined( PRJ_ILI9340_INC_WRITE_CHAR )
+
+// ----------------------------------------------------------------------------------------
+
+inline void select_controller()
+{
+    PRJ_ILI9340_SELECT_PORT &= ~( 1 << PRJ_ILI9340_SELECT_PIN );
+}
+
+inline void deselect_controller()
+{
+    PRJ_ILI9340_SELECT_PORT |= ( 1 << PRJ_ILI9340_SELECT_PIN );
+}
+
+// ----------------------------------------------------------------------------------------
+
+inline void set_controller_command_mode()
+{
+    PRJ_ILI9340_COMMAND_PORT &= ~( 1 << PRJ_ILI9340_COMMAND_PIN );
+}
+
+inline void set_controller_data_mode()
+{
+    PRJ_ILI9340_COMMAND_PORT |= ( 1 << PRJ_ILI9340_COMMAND_PIN );
+}
+
+// ----------------------------------------------------------------------------------------
+
+static void send_command( uint8_t cmd )
+{
+    set_controller_command_mode();
+    select_controller();
+    PORTB &= ~( 1 << PB7 );
+    spi_write( cmd );
+    deselect_controller();
+}
+
+static void send_data( uint8_t data )
+{
+    set_controller_data_mode();
+    PORTB &= ~( 1 << PB7 );
+    select_controller();
+    spi_write( data );
+    deselect_controller();
+}
+
+// ----------------------------------------------------------------------------------------
+
+void ili9340_init()
+{
+    //--    Configure the SPI hardware; the caller might have done it but it doesn't
+    //      hurt to do it again.
+
+    spi_init();
+
+    //--    Configure our select line and mode line.
+
+    PRJ_ILI9340_SELECT_DDR  |= ( 1 << PRJ_ILI9340_SELECT_PIN );
+    PRJ_ILI9340_SELECT_PORT |= ( 1 << PRJ_ILI9340_SELECT_PIN );
+
+    PRJ_ILI9340_COMMAND_DDR  |=  ( 1 << PRJ_ILI9340_COMMAND_PIN );
+    PRJ_ILI9340_COMMAND_PORT &= ~( 1 << PRJ_ILI9340_COMMAND_PIN );
+
+    //--    Configure our reset line (if supplied); it should be pulled high normally.
+
+#if defined( PRJ_ILI9340_USE_HW_RESET )
+
+    PRJ_ILI9340_RESET_DDR  |= ( 1 << PRJ_ILI9340_RESET_PIN );
+    PRJ_ILI9340_RESET_PORT |= ( 1 << PRJ_ILI9340_RESET_PIN );
+
+#endif
+
+    //--    Reset the display.
+
+    ili9340_reset();
+
+    //--    Configure the display controller. This is mostly copied from example code
+    //      and reading the ILITEK datasheet.
+
+    //select_controller();
+
+#if 0
+    send_command(0xEF);
+    send_data(0x03);
+    send_data(0x80);
+    send_data(0x02);
+
+    send_command(0xCF);
+    send_data(0x00);
+    send_data(0XC1);
+    send_data(0X30);
+
+    send_command(0xED);
+    send_data(0x64);
+    send_data(0x03);
+    send_data(0X12);
+    send_data(0X81);
+
+    send_command(0xE8);
+    send_data(0x85);
+    send_data(0x00);
+    send_data(0x78);
+
+    send_command(0xCB);
+    send_data(0x39);
+    send_data(0x2C);
+    send_data(0x00);
+    send_data(0x34);
+    send_data(0x02);
+
+    send_command(0xF7);
+    send_data(0x20);
+
+    send_command(0xEA);
+    send_data(0x00);
+    send_data(0x00);
+#endif
+
+#if 1
+#define ILI9340_PWCTR1  0xC0
+#define ILI9340_PWCTR2  0xC1
+#define ILI9340_PWCTR3  0xC2
+    send_command(ILI9340_PWCTR1);    //Power control 
+    send_data(0x23);   //VRH[5:0] 
+
+    send_command(ILI9340_PWCTR2);    //Power control 
+    send_data(0x10);   //SAP[2:0];BT[3:0] 
+
+#define ILI9340_VMCTR1  0xC5
+    send_command(ILI9340_VMCTR1);    //VCM control 
+    send_data(0x3e); //�Աȶȵ���
+    send_data(0x28);
+
+#define ILI9340_VMCTR2  0xC7
+    send_command(ILI9340_VMCTR2);    //VCM control2 
+    send_data(0x86);  //--
+
+#define ILI9340_MADCTL  0x36
+#define ILI9340_MADCTL_MX  0x40
+#define ILI9340_MADCTL_BGR 0x08
+    send_command(ILI9340_MADCTL);    // Memory Access Control 
+    send_data(ILI9340_MADCTL_MX | ILI9340_MADCTL_BGR);
+
+#define ILI9340_PIXFMT  0x3A
+    send_command(ILI9340_PIXFMT);
+    send_data(0x55);
+
+#define ILI9340_FRMCTR1 0xB1
+    send_command(ILI9340_FRMCTR1);
+    send_data(0x00);
+    send_data(0x18);
+
+#define ILI9340_DFUNCTR 0xB6
+    send_command(ILI9340_DFUNCTR);    // Display Function Control 
+    send_data(0x08);
+    send_data(0x82);
+    send_data(0x27);
+#endif
+
+#if 0
+    send_command( ILI9340_COMMAND_PWCTRL1 );
+    send_data( 0x23 & ILI9340_PWCTRL1_VRH_MASK );   // GVDD = 4.60 V
+    send_data( 0x00 & ILI9340_PWCTRL1_VC_MASK );    // VCI1 = 2.30 V
+
+    send_command( ILI9340_COMMAND_PWCTRL2 );
+    send_data( 0x00 & ILI9340_PWCTRL2_BT_MASK );    // BT = 0b0000
+
+    send_command( ILI9340_COMMAND_VMCTR1 );
+    send_data( 0x3e & ILI9340_VMCTR1_VMH_MASK );    // VCOMH =  4.250 V
+    send_data( 0x28 & ILI9340_VMCTR1_VML_MASK );    // VCOML = -1.500 V
+
+    send_command( ILI9340_COMMAND_VMCTR2 );
+    send_data( ILI9340_VMCTR2_nVM | ( 0x06 & ILI9340_VMCTR2_VMF_MASK ) );
+
+#if defined( PRJ_ILI9340_DISPLAY_ORIENTATION_0 )
+
+    send_command( ILI9340_COMMAND_MADCTL );
+    send_data( ILI9340_MADCTL_MX | ILI9340_MADCTL_BGR );
+
+#elif defined( PRJ_ILI9340_DISPLAY_ORIENTATION_90 )
+
+    send_command( ILI9340_COMMAND_MADCTL );
+    send_data( ILI9340_MADCTL_MV | ILI9340_MADCTL_BGR );
+
+#elif defined( PRJ_ILI9340_DISPLAY_ORIENTATION_180 )
+
+    send_command( ILI9340_COMMAND_MADCTL );
+    send_data( ILI9340_MADCTL_MY | ILI9340_MADCTL_BGR );
+
+#elif defined( PRJ_ILI9340_DISPLAY_ORIENTATION_270 )
+
+    send_command( ILI9340_COMMAND_MADCTL );
+    send_data( ILI9340_MADCTL_MV | ILI9340_MADCTL_MY | ILI9340_MADCTL_MX | ILI9340_MADCTL_BGR );
+
+#endif
+
+    send_command( ILI9340_COMMAND_PIXSET );
+    send_data( ILI9340_PIXSET_16BPP );
+
+    send_command( ILI9340_COMMAND_FRMCTR1 );
+    send_data( 0x00 & ILI9340_FRMCTR1_DIVA_MASK );
+    send_data( 0x18 & ILI9340_FRMCTR1_RTNA_MASK );      // 79 Hz refresh rate
+
+    send_command( ILI9340_COMMAND_DISCTRL );
+    send_data( ILI9340_DISCTRL_PTG_10 | ILI9340_DISCTRL_PT_00 );
+    send_data( ILI9340_DISCTRL_REV | ( 0x02 & ILI9340_DISCTRL_ISC_MASK ) );
+    send_data( 0x27 & ILI9340_DISCTRL_NL_MASK );
+    send_data( 0x00 & ILI9340_DISCTRL_PCDIV_MASK );
+
+    send_command( ILI9340_COMMAND_SLPOUT );
+    spinwait_delay_ms( 120 );
+
+    send_command( ILI9340_COMMAND_DISPON );
+#endif
+
+#if 0
+    send_command(0xF2);    // 3Gamma Function Disable 
+    send_data(0x00);
+
+#define ILI9340_GAMMASET 0x26
+    send_command(ILI9340_GAMMASET);    //Gamma curve selected 
+    send_data(0x01);
+
+#define ILI9340_GMCTRP1 0xE0
+    send_command(ILI9340_GMCTRP1);    //Set Gamma 
+    send_data(0x0F);
+    send_data(0x31);
+    send_data(0x2B);
+    send_data(0x0C);
+    send_data(0x0E);
+    send_data(0x08);
+    send_data(0x4E);
+    send_data(0xF1);
+    send_data(0x37);
+    send_data(0x07);
+    send_data(0x10);
+    send_data(0x03);
+    send_data(0x0E);
+    send_data(0x09);
+    send_data(0x00);
+
+#define ILI9340_GMCTRN1 0xE1
+    send_command(ILI9340_GMCTRN1);    //Set Gamma 
+    send_data(0x00);
+    send_data(0x0E);
+    send_data(0x14);
+    send_data(0x03);
+    send_data(0x11);
+    send_data(0x07);
+    send_data(0x31);
+    send_data(0xC1);
+    send_data(0x48);
+    send_data(0x08);
+    send_data(0x0F);
+    send_data(0x0C);
+    send_data(0x31);
+    send_data(0x36);
+    send_data(0x0F);
+#endif 
+
+#define ILI9340_SLPOUT  0x11
+    send_command(ILI9340_SLPOUT);    //Exit Sleep 
+    spinwait_delay_ms(120);
+#define ILI9340_DISPON  0x29
+    send_command(ILI9340_DISPON);    //Display on
+
+    deselect_controller();
+}
+
+// ----------------------------------------------------------------------------------------
+
+void ili9340_reset()
+{
+
+#if defined( PRJ_ILI9340_USE_HW_RESET )
+
+    spinwait_delay_ms( 5 );
+    PRJ_ILI9340_RESET_PORT &= ~( 1 << PRJ_ILI9340_RESET_PIN );
+    spinwait_delay_ms( 20 );
+    PRJ_ILI9340_RESET_PORT |= ( 1 << PRJ_ILI9340_RESET_PIN );
+    spinwait_delay_ms( 150 );
+
+#else
+
+    //select_controller();
+
+    spinwait_delay_ms( 5 );
+    send_command( ILI9340_COMMAND_SWRESET );
+    spinwait_delay_ms( 150 );
+
+    //deselect_controller();
+
+#endif
+
+}
+
+// ----------------------------------------------------------------------------------------
+//  Prepare to write to the display memory to a given rectangle.
+//
+
+static void prep_page_col_address( uint16_t x, uint16_t dx, uint16_t y, uint16_t dy )
+{
+    send_command( ILI9340_COMMAND_CASET );
+    send_data( static_cast< uint8_t >( x >> 8 ) );
+    send_data( static_cast< uint8_t >( x ) );
+    send_data( static_cast< uint8_t >( ( x + dx ) >> 8 ) );
+    send_data( static_cast< uint8_t >( x + dx ) );
+
+    send_command( ILI9340_COMMAND_PASET );
+    send_data( static_cast< uint8_t >( y >> 8 ) );
+    send_data( static_cast< uint8_t >( y ) );
+    send_data( static_cast< uint8_t >( ( y + dy ) >> 8 ) );
+    send_data( static_cast< uint8_t >( y + dy ) );
+
+    send_command( ILI9340_COMMAND_RAMWR );
+}
+
+
+// ----------------------------------------------------------------------------------------
+
+void ili9340_fill_screen( ili9340_color_t color )
+{
+    //--    Set the page and column addressing to the full screen.
+
+    prep_page_col_address( 0, ILI9340_MAX_HORIZONTAL_PIXELS - 1,
+                           0, ILI9340_MAX_VERTICAL_PIXELS - 1 );
+
+    set_controller_data_mode();
+    select_controller();
+
+    uint8_t color_hi = static_cast< uint8_t >( color >> 8 );
+    uint8_t color_lo = static_cast< uint8_t >( color );
+
+    for ( uint16_t i = 0; i < ILI9340_MAX_HORIZONTAL_PIXELS; ++i )
+    {
+        for ( uint16_t j = 0; j < ILI9340_MAX_VERTICAL_PIXELS; ++j )
+        {
+    PORTB &= ~( 1 << PB7 );
+            spi_write( color_hi );
+    PORTB &= ~( 1 << PB7 );
+            spi_write( color_lo );
+        }
+    }
+
+    deselect_controller();
+}
+
+
+// ----------------------------------------------------------------------------------------
+
+#if defined( PRJ_ILI9340_INC_WRITE_CHAR )
+
+void ili9340_write( char            c, 
+                    uint8_t         x, 
+                    uint8_t         y, 
+                    ili9340_color_t fg_color,
+                    ili9340_color_t bg_color )
+{
+    //--    Convert the glyph coordinates to pixel coordinates.
+
+    uint16_t pixel_x = static_cast< uint16_t >( x ); //% ILI9340_MAX_HORIZONTAL_CHARS );
+    pixel_x *= FONT_8X12_GLYPH_WIDTH;
+
+    uint16_t pixel_y = static_cast< uint16_t >( y ); //% ILI9340_MAX_VERTICAL_CHARS );
+    pixel_y *= FONT_8X12_GLYPH_HEIGHT;
+
+    //--    Fetch the PROGMEM ptr to the desired glyph data.
+
+    const prog_uint8_t* this_glyph = font_8x12_glyph_ptr( c );
+
+    //--    Write the glyph pixels at the specified location.
+
+    uint8_t fg_color_hi = static_cast< uint8_t >( fg_color >> 8 );
+    uint8_t fg_color_lo = static_cast< uint8_t >( fg_color );
+
+    uint8_t bg_color_hi = static_cast< uint8_t >( bg_color >> 8 );
+    uint8_t bg_color_lo = static_cast< uint8_t >( bg_color );
+
+    prep_page_col_address( pixel_x,
+                           static_cast< uint16_t >( FONT_8X12_GLYPH_WIDTH - 1 ),
+                           pixel_y, 
+                           static_cast< uint16_t >( FONT_8X12_GLYPH_HEIGHT - 1 ) );
+
+    set_controller_data_mode();
+    select_controller();
+
+    for ( uint8_t i = 0; i < FONT_8X12_GLYPH_HEIGHT; i++ )
+    {
+        uint8_t data = pgm_read_byte_near( this_glyph++ );
+
+        for ( uint8_t j = 0; j < FONT_8X12_GLYPH_WIDTH; j++ )
+        {
+            if ( data & 0x80 )
+            {
+                spi_write( fg_color_hi );
+                spi_write( fg_color_lo );
+            }
+            else
+            {
+                spi_write( bg_color_hi );
+                spi_write( bg_color_lo );
+            }
+            data <<= 1;
+        }
+    }
+
+    deselect_controller();
+}
+
+#endif
+
+// ----------------------------------------------------------------------------------------
+
+#if defined( PRJ_ILI9340_INC_WRITE_CSTRINGS )
+
+void ili9340_write
+    ( 
+        const char*    str, 
+        uint8_t        x, 
+        uint8_t        y, 
+        ili9340_color_t fg_color,
+        ili9340_color_t bg_color 
+    )
+{
+    while ( *str )
+    {
+        ili9340_write( *str++, x++, y, fg_color, bg_color );
+    }
+}
+
+#endif
+
+// ----------------------------------------------------------------------------------------
+
+#if defined( PRJ_ILI9340_INC_WRITE_PGM_CSTRINGS )
+
+void ili9340_write_pgm
+    ( 
+        const prog_char* str, 
+        uint8_t          x, 
+        uint8_t          y, 
+        ili9340_color_t  fg_color,
+        ili9340_color_t  bg_color 
+    )
+{
+    for ( ;; )
+    {
+        char c = static_cast< char >( pgm_read_byte_near( str++ ) );
+
+        if ( c == '\0' )
+        {
+            return;
+        }
+
+        ili9340_write( c, x++, y, fg_color, bg_color );
+    }
+}
+
+#endif
+
+// ----------------------------------------------------------------------------------------
+
+#if defined( PRJ_ILI9340_INC_SET_PIXEL )
+
+void ili9340_set_pixel( uint8_t x, uint8_t y, ili9340_color_t color )
+{
+    x %= ILI9340_MAX_HORIZONTAL_PIXELS;
+    y %= ILI9340_MAX_VERTICAL_PIXELS;
+
+    select_controller();
+    
+    prep_page_col_address( x, 0, y, 0 );
+    send_data( color );
+    
+    deselect_controller();
+}
+
+#endif
+
+// ----------------------------------------------------------------------------------------
+
+#if defined( PRJ_ILI9340_INC_DRAWING )
+
+void ili9340_line_h( uint16_t x, uint16_t y, uint16_t len, ili9340_color_t color )
+{
+    x %= ILI9340_MAX_HORIZONTAL_PIXELS;
+    y %= ILI9340_MAX_VERTICAL_PIXELS;
+    
+    if ( len + x > ILI9340_MAX_HORIZONTAL_PIXELS )
+    {
+        len = ILI9340_MAX_HORIZONTAL_PIXELS - x;
+    }
+
+    select_controller();
+    
+    prep_page_col_address( x, len - 1, y, 0 );
+    
+    while ( len-- )
+    {
+        send_data( color );
+    }
+
+    deselect_controller();
+}
+
+#endif
+
+// ----------------------------------------------------------------------------------------
+
+#if defined( PRJ_ILI9340_INC_DRAWING )
+
+void ili9340_line_v( uint16_t x, uint16_t y, uint16_t len, ili9340_color_t color )
+{
+    x %= ILI9340_MAX_HORIZONTAL_PIXELS;
+    y %= ILI9340_MAX_VERTICAL_PIXELS;
+    
+    if ( len + y > ILI9340_MAX_VERTICAL_PIXELS )
+    {
+        len = ILI9340_MAX_VERTICAL_PIXELS - y;
+    }
+
+    prep_page_col_address( x, 0, y, len - 1 );
+    
+    set_controller_data_mode();
+    select_controller();
+
+    uint8_t color_hi = static_cast< uint8_t >( color >> 8 );
+    uint8_t color_lo = static_cast< uint8_t >( color );
+
+    while ( len-- )
+    {
+        spi_write( color_hi );
+        spi_write( color_lo );
+    }
+
+    deselect_controller();
+}
+
+#endif
+
+// ----------------------------------------------------------------------------------------
+
+#if defined( PRJ_ILI9340_INC_DRAWING )
+
+void ili9340_rect( uint16_t x, uint16_t y, uint16_t w, uint16_t h, ili9340_color_t color )
+{
+    ili9340_line_h( x, y, w, color );
+    ili9340_line_h( x, y + h - 1, w, color );
+    ili9340_line_v( x, y, h - 1, color );
+    ili9340_line_v( x + w - 1, y, h - 1, color );
+}
+
+#endif
+
+// ----------------------------------------------------------------------------------------
+
+#if defined( PRJ_ILI9340_INC_DRAWING )
+
+void ili9340_fill( uint16_t x, uint16_t y, uint16_t w, uint16_t h, ili9340_color_t color )
+{
+    if ( x > ILI9340_MAX_HORIZONTAL_PIXELS ) { x = ILI9340_MAX_HORIZONTAL_PIXELS; }
+    if ( y > ILI9340_MAX_VERTICAL_PIXELS )   { y = ILI9340_MAX_VERTICAL_PIXELS; }
+    
+    if ( w + x > ILI9340_MAX_HORIZONTAL_PIXELS )
+    {
+        w = ILI9340_MAX_HORIZONTAL_PIXELS - x;
+    }
+
+    if ( h + y > ILI9340_MAX_VERTICAL_PIXELS )
+    {
+        h = ILI9340_MAX_VERTICAL_PIXELS - y;
+    }
+
+    prep_page_col_address( x, w - 1, y, h - 1 );
+
+    set_controller_data_mode();
+    select_controller();
+
+    uint8_t color_hi = static_cast< uint8_t >( color >> 8 );
+    uint8_t color_lo = static_cast< uint8_t >( color );
+
+    for ( uint16_t i = 0; i < w; ++i )
+    {
+        for ( uint16_t j = 0; j < h; ++j )
+        {
+    PORTB &= ~( 1 << PB7 );
+            spi_write( color_hi );
+    PORTB &= ~( 1 << PB7 );
+            spi_write( color_lo );
+        }
+    }
+
+    deselect_controller();
+}
+
+#endif
+
+// ----------------------------------------------------------------------------------------
+
+#if defined( PRJ_ILI9340_INC_DRAWING_ADV )
+
+void ili9340_line( uint16_t x0,
+                   uint16_t y0,x
+                   uint16_t x1, x
+                   uint16_t y1, x
+                   ili9340_color_t color )
+{
+    //--    Bresenham's line rasterization algorithm: 
+    //      http://www.cs.unc.edu/~mcmillan/comp136/Lecture6/Lines.html
+
+    int16_t dy = y1 - y0;
+    int16_t dx = x1 - x0;
+    int8_t  step_x = 1;
+    int8_t  step_y = 1;
+
+    if ( dy < 0 )
+    {
+        dy *= -1;
+        step_y = -1;
+    }
+
+    if ( dx < 0 )
+    {
+        dx *= -1;
+        step_x = -1;
+    }
+
+    dy <<= 1;
+    dx <<= 1;
+
+    ili9340_set_pixel( x0, y0, color );
+
+    if ( dx > dy )
+    {
+        int16_t fraction = dy - ( dx >> 1 );
+
+        while ( x0 != x1 )
+        {
+            if ( fraction >= 0 ) 
+            {
+                y0 += step_y;
+                fraction -= dx;
+            }
+
+            x0 += step_x;
+            fraction += dy;
+
+            ili9340_set_pixel( x0, y0, color );
+        }
+    }
+    else
+    {
+        int16_t fraction = dx - ( dy >> 1 );
+
+        while ( y0 != y1 )
+        {
+            if ( fraction >= 0 )
+            {
+                x0 += step_x;
+                fraction -= dy;
+            }
+
+            y0 += step_y;
+            fraction += dx;
+
+            ili9340_set_pixel( x0, y0, color );
+        }
+    }
+}
+
+#endif
+
+// ----------------------------------------------------------------------------------------
+
+#if defined( PRJ_ILI9340_INC_IMAGEMASK )
+
+void ili9340_imagemask
+    (
+        uint8_t             x,
+        uint8_t             y,
+        uint8_t             w,
+        uint8_t             h,
+        ili9340_color_t      fg_color,
+        ili9340_color_t      bg_color,
+        const prog_uint8_t* pixels
+    )
+{
+    x %= ILI9340_MAX_HORIZONTAL_PIXELS;
+    y %= ILI9340_MAX_VERTICAL_PIXELS;
+
+    select_controller();
+    
+    prep_page_col_address( x, w - 1, y, h - 1 );
+    
+    while ( h-- )
+    {
+        uint8_t pix_bit = 0x00;
+        uint8_t data    = 0x00;
+
+        for ( uint8_t i = 0; i < w; i++ )
+        {
+            if ( pix_bit == 0x00 )
+            {
+                data = pgm_read_byte_near( pixels++ );
+                pix_bit = 0x80;
+            }
+
+            if ( data & pix_bit )
+            {
+                send_data( fg_color );
+            }
+            else
+            {
+                send_data( bg_color );
+            }
+
+            pix_bit >>= 1;
+        }
+    }
+
+    deselect_controller();
+}
+
+#endif
+
+// ----------------------------------------------------------------------------------------
+
+#if defined( PRJ_ILI9340_INC_COLORIMAGE )
+
+void ili9340_colorimage
+    (
+        uint8_t x,
+        uint8_t y,
+        uint8_t w,
+        uint8_t h,
+        const prog_uint8_t* pixels
+    )
+{
+    x %= ILI9340_MAX_HORIZONTAL_PIXELS;
+    y %= ILI9340_MAX_VERTICAL_PIXELS;
+
+    select_controller();
+    
+    prep_page_col_address( x, w - 1, y, h - 1 );
+    
+    while ( h-- )
+    {
+        for ( uint8_t i = 0; i < w; i++ )
+        {
+            send_data( pgm_read_byte_near( pixels++ ) );
+        }
+    }
+
+    deselect_controller();
+}
+
+#endif
+
+// ----------------------------------------------------------------------------------------
+
+#endif  // #if defined( PRJ_ILI9340_ENABLE )
+// ----------------------------------------------------------------------------------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/packages/avr/lcd/ili9340/ili9340.h	Sat Jun 28 16:13:53 2014 -0700
@@ -0,0 +1,288 @@
+// ----------------------------------------------------------------------------------------
+//
+//  avr/lcd/ili9340/ili9340.h
+//    
+//  Bob Cook Development, Robotics Library
+//  http://www.bobcookdev.com/rl/
+//    
+//  This file implements support for the ILI9340 240x320 RGB LCD driver.
+//
+//  Copyright (C) 2014 Bob Cook
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//  
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+//  
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+//
+// ----------------------------------------------------------------------------------------
+
+#if !defined( BCDRL_AVR_LCD_ILI9340_H )
+#define BCDRL_AVR_LCD_ILI9340_H
+
+#include <stdint.h>
+#include <avr/pgmspace.h>
+
+// ----------------------------------------------------------------------------------------
+//  definitions from "project_defs.h"
+//
+//  enable support for the ILI9340 240x320 RGB LCD driver with:
+//
+//      + PRJ_ILI9340_ENABLE
+//
+//  specify the SELECT pin for SPI communications with:
+//
+//      + PRJ_ILI9340_SELECT_PORT
+//      + PRJ_ILI9340_SELECT_PIN
+//      + PRJ_ILI9340_SELECT_DDR
+//
+//  specify the MODE pin (for setting either command or data) with:
+//
+//      + PRJ_ILI9340_COMMAND_PORT
+//      + PRJ_ILI9340_COMMAND_PIN
+//      + PRJ_ILI9340_COMMAND_DDR
+//
+//  specify the (optional) RESET pin with:
+//
+//      + PRJ_ILI9340_USE_HW_RESET
+//      + PRJ_ILI9340_RESET_PORT
+//      + PRJ_ILI9340_RESET_PIN
+//      + PRJ_ILI9340_RESET_DDR
+//
+//  specify the orientation with one (and only one) of the following:
+//
+//      + PRJ_ILI9340_DISPLAY_ORIENTATION_0
+//      + PRJ_ILI9340_DISPLAY_ORIENTATION_90
+//      + PRJ_ILI9340_DISPLAY_ORIENTATION_180
+//      + PRJ_ILI9340_DISPLAY_ORIENTATION_270
+//
+//  optional features can be enabled with one or more of the following:
+//
+//      + PRJ_ILI9340_INC_WRITE_CHAR
+//      + PRJ_ILI9340_INC_WRITE_CSTRINGS
+//      + PRJ_ILI9340_INC_WRITE_PGM_CSTRINGS
+//      + PRJ_ILI9340_INC_SET_PIXEL
+//      + PRJ_ILI9340_INC_DRAWING
+//      + PRJ_ILI9340_INC_IMAGEMASK
+//      + PRJ_ILI9340_INC_COLORIMAGE
+//
+// ----------------------------------------------------------------------------------------
+
+// ----------------------------------------------------------------------------------------
+//  ili9340_color_t
+//
+//      The display driver uses 16-bit color values, packed as: RRRRRGGGGGGBBBBB (5-6-5)
+//
+
+typedef uint16_t ili9340_color_t;
+
+const ili9340_color_t ili9340_color_black   = 0x0000;
+const ili9340_color_t ili9340_color_white   = 0xffff;
+const ili9340_color_t ili9340_color_red     = 0xf800;
+const ili9340_color_t ili9340_color_green   = 0x07e0;
+const ili9340_color_t ili9340_color_blue    = 0x001f;
+const ili9340_color_t ili9340_color_cyan    = 0x07ff;
+const ili9340_color_t ili9340_color_magenta = 0xf81f;
+const ili9340_color_t ili9340_color_yellow  = 0xffe0;
+
+// ----------------------------------------------------------------------------------------
+//  ili9340_init
+//
+//      Initialize the ILI9340 LCD driver package, setting up the hardware and clearing
+//      the display. Call this function prior to any other.
+//
+
+void ili9340_init();
+
+// ----------------------------------------------------------------------------------------
+//  ili9340_reset
+//
+//      Reset the display, using the hardware reset line if available. Otherwise uses
+//      the software reset command.
+//
+
+void ili9340_reset();
+
+// ----------------------------------------------------------------------------------------
+//  ili9340_fill_screen
+//
+//      Fill the entire screen with the specified color. Use this function to erase the
+//      screen by filling it with "ili9340_color_black".
+//
+
+void ili9340_fill_screen( ili9340_color_t color );
+
+// ----------------------------------------------------------------------------------------
+//  ili9340_write
+//
+//      Write a character to the display in the *character* row and column. The maximum
+//      number of characters depends on the orientation:
+//      
+//          horizontal: 53 characters wide x 30 characters high
+//          vertical:   40 characters wide x 40 characters high
+//
+//      This function is only available if PRJ_ILI9340_INC_WRITE_CHAR is defined.
+//
+
+void ili9340_write
+    (
+        char           c, 
+        uint8_t        x, 
+        uint8_t        y, 
+        ili9340_color_t fg_color,
+        ili9340_color_t bg_color 
+    );
+
+// ----------------------------------------------------------------------------------------
+//  ili9340_write
+//
+//      Write a string to the display in the *character* row and column. The display
+//      can support 16 rows by 21 columns.
+//
+//      This function is only available if PRJ_ILI9340_INC_WRITE_CSTRINGS is defined.
+//
+
+void ili9340_write
+    ( 
+        const char*    str, 
+        uint8_t        x, 
+        uint8_t        y, 
+        ili9340_color_t fg_color,
+        ili9340_color_t bg_color 
+    );
+
+// ----------------------------------------------------------------------------------------
+//  ili9340_write_pgm
+//
+//      Write a string from flash memory to the display in the *character* row and column.
+//      The display can support 16 rows by 21 columns.
+//
+//      This function is only available if PRJ_ILI9340_INC_WRITE_PGM_CSTRINGS is defined.
+//
+
+void ili9340_write_pgm
+    ( 
+        const prog_char* str, 
+        uint8_t          x, 
+        uint8_t          y, 
+        ili9340_color_t   fg_color,
+        ili9340_color_t   bg_color 
+    );
+
+// ----------------------------------------------------------------------------------------
+//  ili9340_set_pixel
+//
+//      Paint a specific pixel. The display supports 240 rows by 320 columns.
+//
+//      This function is only available if PRJ_ILI9340_INC_SET_PIXEL is defined.
+//
+
+void ili9340_set_pixel( uint16_t x, uint16_t y, ili9340_color_t color );
+
+// ----------------------------------------------------------------------------------------
+//  ili9340_line_h
+//
+//      Paint a row of pixels.
+//
+//      This function is only available if PRJ_ILI9340_INC_DRAWING is defined.
+//
+
+void ili9340_line_h( uint16_t x, uint16_t y, uint16_t len, ili9340_color_t color );
+
+// ----------------------------------------------------------------------------------------
+//  ili9340_line_v
+//
+//      Paint a column of pixels.
+//
+//      This function is only available if PRJ_ILI9340_INC_DRAWING is defined.
+//
+
+void ili9340_line_v( uint16_t x, uint16_t y, uint16_t len, ili9340_color_t color );
+
+// ----------------------------------------------------------------------------------------
+//  ili9340_rect
+//
+//      Paint the frame (edge) pixels of a rectangular region.
+//
+//      This function is only available if PRJ_ILI9340_INC_DRAWING is defined.
+//
+
+void ili9340_rect( uint16_t x, uint16_t y, uint16_t w, uint16_t h, ili9340_color_t color );
+
+// ----------------------------------------------------------------------------------------
+//  ili9340_fill
+//
+//      Turn on all the pixels inside of a rectangular region.
+//
+//      This function is only available if PRJ_ILI9340_INC_DRAWING is defined.
+//
+
+void ili9340_fill( uint16_t x, uint16_t y, uint16_t w, uint16_t h, ili9340_color_t color );
+
+// ----------------------------------------------------------------------------------------
+//  ili9340_line
+//
+//      Turn on or off the pixels in a line between two points. This is much more
+//      expensive for horizontal or vertical lines; prefer to use the optimized
+//      functions (ili9340_line_h, ili9340_line_v) for those cases when possible.
+//
+//      This function is only available if PRJ_ILI9340_INC_DRAWING is defined.
+//
+
+void ili9340_line( uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, ili9340_color_t color );
+
+// ----------------------------------------------------------------------------------------
+//  ili9340_imagemask
+//
+//      Paint pixels with the specified color using the 1-bit image as a mask, at the
+//      specified pixel location. The pixel data must be stored in flash memory and be 
+//      organized by rows, padded to whole bytes per row.
+//
+//      This function is only available if PRJ_ILI9340_INC_IMAGEMASK is defined.
+//
+
+void ili9340_imagemask
+    (
+        uint16_t             x,
+        uint16_t             y,
+        uint16_t             w,
+        uint16_t             h,
+        ili9340_color_t     fg_color,
+        ili9340_color_t     bg_color,
+        const prog_uint8_t* pixels
+    );
+
+// ----------------------------------------------------------------------------------------
+//  ili9340_colorimage
+//
+//      Paint an image at the specified pixel location. The pixel data must be stored in
+//      flash memory and be organized by rows, padded to an even number of bytes per row.
+//      Each pixel must the 16-bit format: RRRRRGGGGGGBBBBB (5-6-5).
+//
+//      This function is only available if PRJ_ILI9340_INC_COLORIMAGE is defined.
+//
+
+void ili9340_colorimage
+    (
+        uint16_t             x,
+        uint16_t             y,
+        uint16_t             w,
+        uint16_t             h,
+        const prog_uint8_t* pixels
+    );
+
+#endif  // #if !defined( BCDRL_AVR_LCD_ILI9340_H )
+// ----------------------------------------------------------------------------------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/packages/avr/lcd/ili9340/jamfile	Sat Jun 28 16:13:53 2014 -0700
@@ -0,0 +1,41 @@
+# -----------------------------------------------------------------------------------------
+#
+#   avr/lcd/ili9340/jamfile
+#
+#   Bob Cook Development, Robotics Library
+#   http://www.bobcookdev.com/rl/
+#
+#   Copyright (C) 2014 Bob Cook
+#
+#   Permission is hereby granted, free of charge, to any person obtaining a copy
+#   of this software and associated documentation files (the "Software"), to deal
+#   in the Software without restriction, including without limitation the rights
+#   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#   copies of the Software, and to permit persons to whom the Software is
+#   furnished to do so, subject to the following conditions:
+#  
+#   The above copyright notice and this permission notice shall be included in
+#   all copies or substantial portions of the Software.
+#  
+#   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#   THE SOFTWARE.
+#
+# -----------------------------------------------------------------------------------------
+
+if $(TRACE) { echo "trace /packages/avr/lcd/ili9340/jamfile" ; }
+
+SubDir TOP packages avr lcd ili9340 ;
+
+# -----------------------------------------------------------------------------------------
+
+PackageSources
+    ili9340.cpp
+    ;
+    
+# -----------------------------------------------------------------------------------------
+
--- a/main/packages/common/can/can_messages.h	Sat Jun 28 16:12:28 2014 -0700
+++ b/main/packages/common/can/can_messages.h	Sat Jun 28 16:13:53 2014 -0700
@@ -7,7 +7,7 @@
 // 
 //  Identifiers for Controller Area Network messages.
 //
-//  Copyright (c) 2010-2013 Bob Cook
+//  Copyright (c) 2010-2014 Bob Cook
 //
 //  Permission is hereby granted, free of charge, to any person obtaining a copy
 //  of this software and associated documentation files (the "Software"), to deal
@@ -84,6 +84,8 @@
 
 }   can_data_odrctl_button;
 
+const uint16_t can_dataid_odr_start_pgm = 0x0014;
+
 // ----------------------------------------------------------------------------------------
 
 const uint16_t can_dataid_imu_roll  = 0x0020;
@@ -110,8 +112,14 @@
 
 // ----------------------------------------------------------------------------------------
 
-const uint16_t can_dataid_latitude  = 0x0101;
-const uint16_t can_dataid_longitude = 0x0102;
+const uint16_t can_dataid_gps_latitude  = 0x0101;
+const uint16_t can_dataid_gps_longitude = 0x0102;
+
+const uint16_t can_dataid_origin_lat  = 0x0104;
+const uint16_t can_dataid_origin_long = 0x0105;
+
+const uint16_t can_dataid_target_lat  = 0x0106;
+const uint16_t can_dataid_target_long = 0x0107;
 
 const int32_t  can_data_gps_min_multiplier = 1000;
 
--- a/main/packages/common/can/can_nodes.h	Sat Jun 28 16:12:28 2014 -0700
+++ b/main/packages/common/can/can_nodes.h	Sat Jun 28 16:13:53 2014 -0700
@@ -46,6 +46,7 @@
 const uint8_t can_node_odr_controller  = 0x0002;
 const uint8_t can_node_odr_sonar_front = 0x0003;
 const uint8_t can_node_odr_motion      = 0x0004;
+const uint8_t can_node_odr_display     = 0x0005;
 
 // ----------------------------------------------------------------------------------------
 //  sensor modules
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/packages/common/font/font_6x8.cpp	Sat Jun 28 16:13:53 2014 -0700
@@ -0,0 +1,180 @@
+// ----------------------------------------------------------------------------------------
+//
+//  avr/lcd/ili9340/ili9340.cpp
+//    
+//  Bob Cook Development, Robotics Library
+//  http://www.bobcookdev.com/rl/
+//    
+//  A bitmap font, 6 pixels wide by 8 pixels high.
+//
+//  Copyright (C) 2014 Bob Cook
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//  
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+//  
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+//
+//  Portions copied from "Nokia 6100 LCD Display Driver" written by James P. Lynch.
+//  See http://www.sparkfun.com/tutorial/Nokia%206100%20LCD%20Display%20Driver.pdf
+//
+// ----------------------------------------------------------------------------------------
+
+#include "font_defs.h"
+
+#include "project_defs.h"
+
+#if defined( __AVR_ARCH__ )
+
+// always after other includes
+#include "packages/avr/device/workaround34734.h"
+
+#endif // #if defined( __AVR_ARCH__ )
+
+
+// ----------------------------------------------------------------------------------------
+
+#if defined( PRJ_FONT_6X8_ENABLE )
+
+typedef uint8_t font_glyph_data_t[ 8 ];
+
+#if defined( __AVR_ARCH__ )
+
+static const font_glyph_data_t PROGMEM glyphs[] =
+
+#else
+
+static const font_glyph_data_t glyphs[] =
+
+#endif // #if defined( __AVR_ARCH__ )
+
+{
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },     // space, ascii 0x20
+    { 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x10, 0x00 },     // !
+    { 0x28, 0x28, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00 },     // "
+    { 0x28, 0x28, 0x7c, 0x28, 0x7c, 0x28, 0x28, 0x00 },     // #
+    { 0x10, 0x3c, 0x50, 0x38, 0x14, 0x78, 0x10, 0x00 },     // $
+    { 0x60, 0x64, 0x08, 0x10, 0x20, 0x4c, 0x0c, 0x00 },     // %
+    { 0x10, 0x28, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00 },     // &
+    { 0x30, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00 },     // '
+    { 0x10, 0x20, 0x40, 0x40, 0x40, 0x20, 0x10, 0x00 },     // (
+    { 0x10, 0x08, 0x04, 0x04, 0x04, 0x08, 0x10, 0x00 },     // )
+    { 0x00, 0x10, 0x54, 0x38, 0x54, 0x10, 0x00, 0x00 },     // *
+    { 0x00, 0x10, 0x10, 0x7c, 0x10, 0x10, 0x00, 0x00 },     // +
+    { 0x00, 0x00, 0x00, 0x00, 0x30, 0x10, 0x20, 0x00 },     // ,
+    { 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00 },     // -
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00 },     // .
+    { 0x00, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, 0x00 },     // /
+    { 0x38, 0x44, 0x4c, 0x54, 0x64, 0x44, 0x38, 0x00 },     // 0
+    { 0x10, 0x30, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00 },     // 1
+    { 0x38, 0x44, 0x04, 0x08, 0x10, 0x20, 0x7c, 0x00 },     // 2
+    { 0x7c, 0x08, 0x10, 0x08, 0x04, 0x44, 0x38, 0x00 },     // 3
+    { 0x08, 0x18, 0x28, 0x48, 0x7c, 0x08, 0x08, 0x00 },     // 4
+    { 0x7c, 0x40, 0x78, 0x04, 0x04, 0x44, 0x38, 0x00 },     // 5
+    { 0x18, 0x20, 0x40, 0x78, 0x44, 0x44, 0x38, 0x00 },     // 6
+    { 0x7c, 0x04, 0x08, 0x10, 0x20, 0x20, 0x20, 0x00 },     // 7
+    { 0x38, 0x44, 0x44, 0x38, 0x44, 0x44, 0x38, 0x00 },     // 8
+    { 0x38, 0x44, 0x44, 0x38, 0x04, 0x04, 0x38, 0x00 },     // 9
+    { 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x00, 0x00 },     // :
+    { 0x00, 0x30, 0x30, 0x00, 0x30, 0x10, 0x20, 0x00 },     // ;
+    { 0x08, 0x10, 0x20, 0x40, 0x20, 0x10, 0x08, 0x00 },     // <
+    { 0x00, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x00, 0x00 },     // =
+    { 0x20, 0x10, 0x08, 0x04, 0x08, 0x10, 0x20, 0x00 },     // >
+    { 0x38, 0x44, 0x04, 0x08, 0x10, 0x00, 0x10, 0x00 },     // ?
+    { 0x38, 0x44, 0x04, 0x34, 0x54, 0x54, 0x38, 0x00 },     // @
+    { 0x38, 0x44, 0x44, 0x44, 0x7c, 0x44, 0x44, 0x00 },     // A
+    { 0x78, 0x44, 0x44, 0x78, 0x44, 0x44, 0x78, 0x00 },     // B
+    { 0x38, 0x44, 0x40, 0x40, 0x40, 0x44, 0x38, 0x00 },     // C
+    { 0x70, 0x48, 0x44, 0x44, 0x44, 0x48, 0x70, 0x00 },     // D
+    { 0x7c, 0x40, 0x40, 0x78, 0x40, 0x40, 0x7c, 0x00 },     // E
+    { 0x7c, 0x40, 0x40, 0x78, 0x40, 0x40, 0x40, 0x00 },     // F
+    { 0x38, 0x44, 0x40, 0x5c, 0x44, 0x44, 0x3c, 0x00 },     // G
+    { 0x44, 0x44, 0x44, 0x7c, 0x44, 0x44, 0x44, 0x00 },     // H
+    { 0x38, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00 },     // I
+    { 0x1c, 0x08, 0x08, 0x08, 0x08, 0x48, 0x30, 0x00 },     // J
+    { 0x44, 0x48, 0x50, 0x60, 0x50, 0x48, 0x44, 0x00 },     // K
+    { 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7c, 0x00 },     // L
+    { 0x44, 0x6c, 0x54, 0x54, 0x44, 0x44, 0x44, 0x00 },     // M
+    { 0x44, 0x44, 0x64, 0x54, 0x4c, 0x44, 0x44, 0x00 },     // N
+    { 0x38, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00 },     // O
+    { 0x78, 0x44, 0x44, 0x78, 0x40, 0x40, 0x40, 0x00 },     // P
+    { 0x38, 0x44, 0x44, 0x44, 0x54, 0x48, 0x34, 0x00 },     // Q
+    { 0x78, 0x44, 0x44, 0x78, 0x50, 0x48, 0x44, 0x00 },     // R
+    { 0x3c, 0x40, 0x40, 0x38, 0x04, 0x04, 0x78, 0x00 },     // S
+    { 0x7c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00 },     // T
+    { 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00 },     // U
+    { 0x44, 0x44, 0x44, 0x44, 0x44, 0x28, 0x10, 0x00 },     // V
+    { 0x44, 0x44, 0x44, 0x54, 0x54, 0x54, 0x28, 0x00 },     // W
+    { 0x44, 0x44, 0x28, 0x10, 0x28, 0x44, 0x44, 0x00 },     // X
+    { 0x44, 0x44, 0x44, 0x28, 0x10, 0x10, 0x10, 0x00 },     // Y
+    { 0x7c, 0x04, 0x08, 0x10, 0x20, 0x40, 0x7c, 0x00 },     // Z
+    { 0x70, 0x40, 0x40, 0x40, 0x40, 0x40, 0x70, 0x00 },     // [
+    { 0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00 },     // \ (backslash)
+    { 0x1c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1c, 0x00 },     // ]
+    { 0x10, 0x28, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00 },     // ^
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00 },     // _
+    { 0x20, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 },     // `
+    { 0x00, 0x00, 0x38, 0x04, 0x3c, 0x44, 0x3c, 0x00 },     // a
+    { 0x40, 0x40, 0x58, 0x64, 0x44, 0x44, 0x78, 0x00 },     // b
+    { 0x00, 0x00, 0x38, 0x40, 0x40, 0x44, 0x38, 0x00 },     // c
+    { 0x04, 0x04, 0x34, 0x4c, 0x44, 0x44, 0x3c, 0x00 },     // d
+    { 0x00, 0x00, 0x38, 0x44, 0x7c, 0x40, 0x38, 0x00 },     // e
+    { 0x18, 0x24, 0x20, 0x70, 0x20, 0x20, 0x20, 0x00 },     // f
+    { 0x00, 0x3c, 0x44, 0x44, 0x3c, 0x04, 0x38, 0x00 },     // g
+    { 0x40, 0x40, 0x58, 0x64, 0x44, 0x44, 0x44, 0x00 },     // h
+    { 0x10, 0x00, 0x30, 0x10, 0x10, 0x10, 0x38, 0x00 },     // i
+    { 0x04, 0x00, 0x0c, 0x04, 0x04, 0x24, 0x18, 0x00 },     // j
+    { 0x40, 0x40, 0x48, 0x50, 0x60, 0x50, 0x48, 0x00 },     // k
+    { 0x30, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00 },     // l
+    { 0x00, 0x00, 0x68, 0x54, 0x54, 0x44, 0x44, 0x00 },     // m
+    { 0x00, 0x00, 0x58, 0x64, 0x44, 0x44, 0x44, 0x00 },     // n
+    { 0x00, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00 },     // o
+    { 0x00, 0x00, 0x78, 0x44, 0x78, 0x40, 0x40, 0x00 },     // p
+    { 0x00, 0x00, 0x34, 0x4c, 0x3c, 0x04, 0x04, 0x00 },     // q
+    { 0x00, 0x00, 0x58, 0x64, 0x40, 0x40, 0x40, 0x00 },     // r
+    { 0x00, 0x00, 0x38, 0x40, 0x38, 0x04, 0x78, 0x00 },     // s
+    { 0x20, 0x20, 0x70, 0x20, 0x20, 0x24, 0x18, 0x00 },     // t
+    { 0x00, 0x00, 0x44, 0x44, 0x44, 0x4c, 0x34, 0x00 },     // u
+    { 0x00, 0x00, 0x44, 0x44, 0x44, 0x28, 0x10, 0x00 },     // v
+    { 0x00, 0x00, 0x44, 0x44, 0x54, 0x54, 0x28, 0x00 },     // w
+    { 0x00, 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00 },     // x
+    { 0x00, 0x00, 0x44, 0x44, 0x3c, 0x04, 0x38, 0x00 },     // y
+    { 0x00, 0x00, 0x7c, 0x08, 0x10, 0x20, 0x7c, 0x00 },     // z
+    { 0x10, 0x20, 0x20, 0x40, 0x20, 0x20, 0x10, 0x00 },     // {
+    { 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00 },     // |
+    { 0x10, 0x08, 0x08, 0x04, 0x08, 0x08, 0x10, 0x00 },     // }
+    { 0x00, 0x00, 0x14, 0x28, 0x00, 0x00, 0x00, 0x00 },     // ~
+    { 0x00, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x00 }      // DEL
+
+};
+
+// ----------------------------------------------------------------------------------------
+
+const font_glypt_t* font_6x8_glyph_ptr( char which )
+{
+    if ( which < 0x20 )
+    {
+        which = 0x7f;   // generic character glyph
+    }
+
+    which -= 0x20;
+
+    return reinterpret_cast< const font_glypt_t* >(
+                                            &glyphs[ static_cast< uint8_t >( which ) ] );
+}
+
+#endif  // #if defined( PRJ_FONT_6x8_ENABLE )
+// ----------------------------------------------------------------------------------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/packages/common/font/font_8x12.cpp	Sat Jun 28 16:13:53 2014 -0700
@@ -0,0 +1,179 @@
+// ----------------------------------------------------------------------------------------
+//
+//  common/font/font_8x12.cpp
+//    
+//  Bob Cook Development, Robotics Library
+//  http://www.bobcookdev.com/rl/
+// 
+//  A bitmap font, 8 pixels wide by 12 pixels high.
+//
+//  Copyright (C) 2014 Bob Cook
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//  
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+//  
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+//
+//  Portions copied from mikrocontroller.net
+//  See http://www.mikrocontroller.net/attachment/52040/font.zip
+//
+// ----------------------------------------------------------------------------------------
+
+#include "font_defs.h"
+
+#include "project_defs.h"
+
+#if defined( __AVR_ARCH__ )
+
+// always after other includes
+#include "packages/avr/device/workaround34734.h"
+
+#endif // #if defined( __AVR_ARCH__ )
+
+
+// ----------------------------------------------------------------------------------------
+
+#if defined( PRJ_FONT_8X12_ENABLE )
+
+typedef uint8_t font_glyph_data_t[ 16 ];
+
+#if defined( __AVR_ARCH__ )
+
+static const font_glyph_data_t PROGMEM glyphs[] =
+
+#else
+
+static const font_glyph_data_t glyphs[] =
+
+#endif // #if defined( __AVR_ARCH__ )
+
+{
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // space
+    { 0x00, 0x30, 0x78, 0x78, 0x78, 0x30, 0x30, 0x00, 0x30, 0x30, 0x00, 0x00 }, // !
+    { 0x00, 0x66, 0x66, 0x66, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // "
+    { 0x00, 0x6C, 0x6C, 0xFE, 0x6C, 0x6C, 0x6C, 0xFE, 0x6C, 0x6C, 0x00, 0x00 }, // #
+    { 0x30, 0x30, 0x7C, 0xC0, 0xC0, 0x78, 0x0C, 0x0C, 0xF8, 0x30, 0x30, 0x00 }, // $
+    { 0x00, 0x00, 0x00, 0xC4, 0xCC, 0x18, 0x30, 0x60, 0xCC, 0x8C, 0x00, 0x00 }, // %
+    { 0x00, 0x70, 0xD8, 0xD8, 0x70, 0xFA, 0xDE, 0xCC, 0xDC, 0x76, 0x00, 0x00 }, // &
+    { 0x00, 0x30, 0x30, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // '
+    { 0x00, 0x0C, 0x18, 0x30, 0x60, 0x60, 0x60, 0x30, 0x18, 0x0C, 0x00, 0x00 }, // (
+    { 0x00, 0x60, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0x60, 0x00, 0x00 }, // )
+    { 0x00, 0x00, 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00, 0x00, 0x00 }, // *
+    { 0x00, 0x00, 0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00 }, // +
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x38, 0x60, 0x00 }, // ,
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // -
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x38, 0x00, 0x00 }, // .
+    { 0x00, 0x00, 0x02, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x80, 0x00, 0x00 }, // /
+    { 0x00, 0x7C, 0xC6, 0xCE, 0xDE, 0xD6, 0xF6, 0xE6, 0xC6, 0x7C, 0x00, 0x00 }, // 0
+    { 0x00, 0x10, 0x30, 0xF0, 0x30, 0x30, 0x30, 0x30, 0x30, 0xFC, 0x00, 0x00 }, // 1
+    { 0x00, 0x78, 0xCC, 0xCC, 0x0C, 0x18, 0x30, 0x60, 0xCC, 0xFC, 0x00, 0x00 }, // 2
+    { 0x00, 0x78, 0xCC, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0xCC, 0x78, 0x00, 0x00 }, // 3
+    { 0x00, 0x0C, 0x1C, 0x3C, 0x6C, 0xCC, 0xFE, 0x0C, 0x0C, 0x1E, 0x00, 0x00 }, // 4
+    { 0x00, 0xFC, 0xC0, 0xC0, 0xC0, 0xF8, 0x0C, 0x0C, 0xCC, 0x78, 0x00, 0x00 }, // 5
+    { 0x00, 0x38, 0x60, 0xC0, 0xC0, 0xF8, 0xCC, 0xCC, 0xCC, 0x78, 0x00, 0x00 }, // 6
+    { 0x00, 0xFE, 0xC6, 0xC6, 0x06, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x00, 0x00 }, // 7
+    { 0x00, 0x78, 0xCC, 0xCC, 0xCC, 0x78, 0xCC, 0xCC, 0xCC, 0x78, 0x00, 0x00 }, // 8
+    { 0x00, 0x78, 0xCC, 0xCC, 0xCC, 0x7C, 0x18, 0x18, 0x30, 0x70, 0x00, 0x00 }, // 9
+    { 0x00, 0x00, 0x00, 0x38, 0x38, 0x00, 0x00, 0x38, 0x38, 0x00, 0x00, 0x00 }, // :
+    { 0x00, 0x00, 0x00, 0x38, 0x38, 0x00, 0x00, 0x38, 0x38, 0x18, 0x30, 0x00 }, // ;
+    { 0x00, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x00, 0x00 }, // <
+    { 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00 }, // =
+    { 0x00, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x00, 0x00 }, // >
+    { 0x00, 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x30, 0x00, 0x30, 0x30, 0x00, 0x00 }, // ?
+    { 0x00, 0x7C, 0xC6, 0xC6, 0xDE, 0xDE, 0xDE, 0xC0, 0xC0, 0x7C, 0x00, 0x00 }, // @
+    { 0x00, 0x30, 0x78, 0xCC, 0xCC, 0xCC, 0xFC, 0xCC, 0xCC, 0xCC, 0x00, 0x00 }, // A
+    { 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x66, 0xFC, 0x00, 0x00 }, // B
+    { 0x00, 0x3C, 0x66, 0xC6, 0xC0, 0xC0, 0xC0, 0xC6, 0x66, 0x3C, 0x00, 0x00 }, // C
+    { 0x00, 0xF8, 0x6C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6C, 0xF8, 0x00, 0x00 }, // D
+    { 0x00, 0xFE, 0x62, 0x60, 0x64, 0x7C, 0x64, 0x60, 0x62, 0xFE, 0x00, 0x00 }, // E
+    { 0x00, 0xFE, 0x66, 0x62, 0x64, 0x7C, 0x64, 0x60, 0x60, 0xF0, 0x00, 0x00 }, // F
+    { 0x00, 0x3C, 0x66, 0xC6, 0xC0, 0xC0, 0xCE, 0xC6, 0x66, 0x3E, 0x00, 0x00 }, // G
+    { 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xFC, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, 0x00 }, // H
+    { 0x00, 0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, 0x00 }, // I
+    { 0x00, 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0xCC, 0xCC, 0xCC, 0x78, 0x00, 0x00 }, // J
+    { 0x00, 0xE6, 0x66, 0x6C, 0x6C, 0x78, 0x6C, 0x6C, 0x66, 0xE6, 0x00, 0x00 }, // K
+    { 0x00, 0xF0, 0x60, 0x60, 0x60, 0x60, 0x62, 0x66, 0x66, 0xFE, 0x00, 0x00 }, // L
+    { 0x00, 0xC6, 0xEE, 0xFE, 0xFE, 0xD6, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00 }, // M
+    { 0x00, 0xC6, 0xC6, 0xE6, 0xF6, 0xFE, 0xDE, 0xCE, 0xC6, 0xC6, 0x00, 0x00 }, // N
+    { 0x00, 0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x00, 0x00 }, // O
+    { 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x60, 0xF0, 0x00, 0x00 }, // P
+    { 0x00, 0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0xCE, 0xDE, 0x7C, 0x0C, 0x1E, 0x00 }, // Q
+    { 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x6C, 0x66, 0x66, 0xE6, 0x00, 0x00 }, // R
+    { 0x00, 0x78, 0xCC, 0xCC, 0xC0, 0x70, 0x18, 0xCC, 0xCC, 0x78, 0x00, 0x00 }, // S
+    { 0x00, 0xFC, 0xB4, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, 0x00 }, // T
+    { 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x78, 0x00, 0x00 }, // U
+    { 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x00, 0x00 }, // V
+    { 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xD6, 0xD6, 0x6C, 0x6C, 0x6C, 0x00, 0x00 }, // W
+    { 0x00, 0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x78, 0xCC, 0xCC, 0xCC, 0x00, 0x00 }, // X
+    { 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x30, 0x30, 0x78, 0x00, 0x00 }, // Y
+    { 0x00, 0xFE, 0xCE, 0x98, 0x18, 0x30, 0x60, 0x62, 0xC6, 0xFE, 0x00, 0x00 }, // Z
+    { 0x00, 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00, 0x00 }, // [
+    { 0x00, 0x00, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x02, 0x00, 0x00 }, // \ (backslash)
+    { 0x00, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x00, 0x00 }, // ]
+    { 0x10, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ^
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00 }, // _
+    { 0x30, 0x30, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // `
+    { 0x00, 0x00, 0x00, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0xCC, 0x76, 0x00, 0x00 }, // a
+    { 0x00, 0xE0, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x66, 0xDC, 0x00, 0x00 }, // b
+    { 0x00, 0x00, 0x00, 0x00, 0x78, 0xCC, 0xC0, 0xC0, 0xCC, 0x78, 0x00, 0x00 }, // c
+    { 0x00, 0x1C, 0x0C, 0x0C, 0x7C, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00 }, // d
+    { 0x00, 0x00, 0x00, 0x00, 0x78, 0xCC, 0xFC, 0xC0, 0xCC, 0x78, 0x00, 0x00 }, // e
+    { 0x00, 0x38, 0x6C, 0x60, 0x60, 0xF8, 0x60, 0x60, 0x60, 0xF0, 0x00, 0x00 }, // f
+    { 0x00, 0x00, 0x00, 0x00, 0x76, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0xCC, 0x78 }, // g
+    { 0x00, 0xE0, 0x60, 0x60, 0x6C, 0x76, 0x66, 0x66, 0x66, 0xE6, 0x00, 0x00 }, // h
+    { 0x00, 0x18, 0x18, 0x00, 0x78, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00, 0x00 }, // i
+    { 0x00, 0x0C, 0x0C, 0x00, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0xCC, 0xCC, 0x78 }, // j
+    { 0x00, 0xE0, 0x60, 0x60, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0xE6, 0x00, 0x00 }, // k
+    { 0x00, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00, 0x00 }, // l
+    { 0x00, 0x00, 0x00, 0x00, 0xFC, 0xD6, 0xD6, 0xD6, 0xD6, 0xC6, 0x00, 0x00 }, // m
+    { 0x00, 0x00, 0x00, 0x00, 0xF8, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, 0x00 }, // n
+    { 0x00, 0x00, 0x00, 0x00, 0x78, 0xCC, 0xCC, 0xCC, 0xCC, 0x78, 0x00, 0x00 }, // o
+    { 0x00, 0x00, 0x00, 0x00, 0xDC, 0x66, 0x66, 0x66, 0x66, 0x7C, 0x60, 0xF0 }, // p
+    { 0x00, 0x00, 0x00, 0x00, 0x76, 0xCC, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0x1E }, // q
+    { 0x00, 0x00, 0x00, 0x00, 0xEC, 0x6E, 0x76, 0x60, 0x60, 0xF0, 0x00, 0x00 }, // r
+    { 0x00, 0x00, 0x00, 0x00, 0x78, 0xCC, 0x60, 0x18, 0xCC, 0x78, 0x00, 0x00 }, // s
+    { 0x00, 0x00, 0x20, 0x60, 0xFC, 0x60, 0x60, 0x60, 0x6C, 0x38, 0x00, 0x00 }, // t
+    { 0x00, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00 }, // u
+    { 0x00, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x00, 0x00 }, // v
+    { 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xD6, 0xD6, 0x6C, 0x6C, 0x00, 0x00 }, // w
+    { 0x00, 0x00, 0x00, 0x00, 0xC6, 0x6C, 0x38, 0x38, 0x6C, 0xC6, 0x00, 0x00 }, // x
+    { 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x0C, 0x18, 0xF0 }, // y
+    { 0x00, 0x00, 0x00, 0x00, 0xFC, 0x8C, 0x18, 0x60, 0xC4, 0xFC, 0x00, 0x00 }, // z
+    { 0x00, 0x1C, 0x30, 0x30, 0x60, 0xC0, 0x60, 0x30, 0x30, 0x1C, 0x00, 0x00 }, // {
+    { 0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00 }, // |
+    { 0x00, 0xE0, 0x30, 0x30, 0x18, 0x0C, 0x18, 0x30, 0x30, 0xE0, 0x00, 0x00 }, // }
+    { 0x00, 0x73, 0xDA, 0xCE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ~
+    { 0x00, 0x00, 0x00, 0x10, 0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0x00, 0x00, 0x00 }  // DEL
+};
+
+// ----------------------------------------------------------------------------------------
+
+const font_glypt_t* font_8x12_glyph_ptr( char which )
+{
+    if ( which < 0x20 )
+    {
+        which = 0x7f;   // generic character glyph
+    }
+
+    which -= 0x20;
+
+    return reinterpret_cast< const font_glypt_t* >(
+                                            &glyphs[ static_cast< uint8_t >( which ) ] );
+}
+
+#endif  // #if defined( PRJ_FONT_8X12_ENABLE )
+// ----------------------------------------------------------------------------------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/packages/common/font/font_defs.h	Sat Jun 28 16:13:53 2014 -0700
@@ -0,0 +1,69 @@
+// ----------------------------------------------------------------------------------------
+//
+//  common/font/font_defs.h
+//    
+//  Bob Cook Development, Robotics Library
+//  http://www.bobcookdev.com/rl/
+// 
+//  Bitmap font definitions.
+//
+//  Copyright (C) 2014 Bob Cook
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//  
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+//  
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+//
+// ----------------------------------------------------------------------------------------
+
+#if defined( __AVR_ARCH__ )
+
+#include <avr/pgmspace.h>
+
+#endif // #if defined( __AVR_ARCH__ )
+
+// ----------------------------------------------------------------------------------------
+
+#if defined( __AVR_ARCH__ )
+
+typedef prog_uint8_t font_glypt_t;
+
+#else
+
+typedef char font_glypt_t;
+
+#endif // #if defined( __AVR_ARCH__ )
+
+// ----------------------------------------------------------------------------------------
+//
+//  PRJ_FONT_6X8_ENABLE
+
+const uint8_t FONT_6X8_GLYPH_WIDTH  = 6;
+const uint8_t FONT_6X8_GLYPH_HEIGHT = 8;
+
+const font_glypt_t* font_6x8_glyph_ptr( char which );
+
+// ----------------------------------------------------------------------------------------
+//
+//  PRJ_FONT_8X12_ENABLE
+
+const uint8_t FONT_8X12_GLYPH_WIDTH  = 8;
+const uint8_t FONT_8X12_GLYPH_HEIGHT = 12;
+
+const font_glypt_t* font_8x12_glyph_ptr( char which );
+
+// ----------------------------------------------------------------------------------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/packages/common/font/jamfile	Sat Jun 28 16:13:53 2014 -0700
@@ -0,0 +1,42 @@
+# -----------------------------------------------------------------------------------------
+#
+#   common/font/jamfile
+#
+#   Bob Cook Development, Robotics Library
+#   http://www.bobcookdev.com/rl/
+#
+#   Copyright (c) 2014 Bob Cook
+# 
+#   Permission is hereby granted, free of charge, to any person obtaining a copy
+#   of this software and associated documentation files (the "Software"), to deal
+#   in the Software without restriction, including without limitation the rights
+#   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#   copies of the Software, and to permit persons to whom the Software is
+#   furnished to do so, subject to the following conditions:
+# 
+#   The above copyright notice and this permission notice shall be included in
+#   all copies or substantial portions of the Software.
+# 
+#   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#   THE SOFTWARE.
+#
+# -----------------------------------------------------------------------------------------
+
+if $(TRACE) { echo "trace /packages/common/font/jamfile" ; }
+
+SubDir TOP packages common font ;
+
+# -----------------------------------------------------------------------------------------
+
+PackageSources
+    font_6x8.cpp
+    font_8x12.cpp
+    ;
+    
+# -----------------------------------------------------------------------------------------
+
--- a/main/packages/common/gps/nmea.cpp	Sat Jun 28 16:12:28 2014 -0700
+++ b/main/packages/common/gps/nmea.cpp	Sat Jun 28 16:13:53 2014 -0700
@@ -237,6 +237,16 @@
         return;
     }
 
+    g_longitude_min = convert_digit_char( *buffer++ );
+
+    if ( ! is_digit_char( *buffer ) )
+    {
+        return;
+    }
+
+    g_longitude_min *= 10;
+    g_longitude_min += convert_digit_char( *buffer++ );
+
     if ( *buffer != '.' )
     {
         return;
--- a/main/packages/common/jamdefs	Sat Jun 28 16:12:28 2014 -0700
+++ b/main/packages/common/jamdefs	Sat Jun 28 16:13:53 2014 -0700
@@ -31,7 +31,7 @@
 
 # -----------------------------------------------------------------------------------------
 
-DefinePackages TOP packages common : can filt gps pololu test util ;
+DefinePackages TOP packages common : can filt font gps pololu test util ;
 
 # -----------------------------------------------------------------------------------------
 
--- a/main/robots/odr-controller/canmsgs.cpp	Sat Jun 28 16:12:28 2014 -0700
+++ b/main/robots/odr-controller/canmsgs.cpp	Sat Jun 28 16:13:53 2014 -0700
@@ -32,6 +32,7 @@
 
 #include "project_defs.h"
 #include "func.h"
+#include "leds.h"
 
 #include "packages/avr/can/mcp2515.h"
 #include "packages/avr/device/spinwait.h"
@@ -158,6 +159,30 @@
 
 // ----------------------------------------------------------------------------------------
 
+#if 0
+static void update_latitude( const can_data_gps_data* data )
+{
+    display_update_latitude( data->degrees, data->min_thousandths );
+}
+
+// ----------------------------------------------------------------------------------------
+
+static void update_longitude( const can_data_gps_data* data )
+{
+    display_update_longitude( data->degrees, data->min_thousandths );
+}
+
+// ----------------------------------------------------------------------------------------
+
+static void update_gps_fix( const can_data_gps_fix* data )
+{
+    status_got_gps_fix( data->satellites > 0 );
+    display_update_gps_fix( data->satellites );
+}
+#endif
+
+// ----------------------------------------------------------------------------------------
+
 static void got_mgr_update( const can_data_odrmgr_update* data, uint8_t length )
 {
     status_got_mgr_update();
@@ -192,6 +217,8 @@
         return true; // not for us, everything ok
     }
 
+//    led_yellow_toggle();
+
     switch ( dataid )
     {
         case can_dataid_heartbeat:
@@ -227,6 +254,20 @@
             update_roll( reinterpret_cast< can_data_imu_data* >( &recvdata ) );
             break;
 
+#if 0
+        case can_dataid_latitude:
+            update_latitude( reinterpret_cast< can_data_gps_data* >( &recvdata ) );
+            break;
+
+        case can_dataid_longitude:
+            update_longitude( reinterpret_cast< can_data_gps_data* >( &recvdata ) );
+            break;
+
+        case can_dataid_gps_fix:
+            update_gps_fix( reinterpret_cast< can_data_gps_fix* >( &recvdata ) );
+            break;
+#endif
+
         case can_dataid_sonar_front_state_enabled:
             status_got_sonar_front_enabled();
             break;
--- a/main/robots/odr-controller/display.cpp	Sat Jun 28 16:12:28 2014 -0700
+++ b/main/robots/odr-controller/display.cpp	Sat Jun 28 16:13:53 2014 -0700
@@ -56,11 +56,10 @@
 //      7-|  front rangefinder  |-
 //      8-|   000 000 000 000   |-
 //      9-|                     |-
-//     10-| Y+000 P+00.0 R+00.0 |-
-//      1-| Target:+000 (+000)  |-
-//      1-|                     |-
+//     10-| lat: N 123 45.678 * |-
+//      1-| lon: W 123 45.678   |-
 //      2-|                     |-
-//      3-|                     |-
+//      3-| Y+000 P+00.0 R+00.0 |-
 //      4-|                     |-
 //      5-|ESTOP -A- -B-        |-
 //         ||||||||||||||||||||| 
@@ -137,7 +136,9 @@
     sfe569_write_pgm( PSTR("motion controller"), 2, 4, sfe569_color_white, sfe569_color_blue );
     sfe569_write_pgm( PSTR("f:   /    r:   /"), 1, 5, sfe569_color_white, sfe569_color_blue );
     sfe569_write_pgm( PSTR("front rangefinder"), 2, 7, sfe569_color_white, sfe569_color_blue );
-    sfe569_write_pgm( PSTR("Y---- P----- R-----"), 1, 10, sfe569_color_white, sfe569_color_blue );
+    sfe569_write_pgm( PSTR("lat: - ----------"), 1, 10, sfe569_color_white, sfe569_color_blue );
+    sfe569_write_pgm( PSTR("lon: - ----------"), 1, 11, sfe569_color_white, sfe569_color_blue );
+    sfe569_write_pgm( PSTR("Y---- P----- R-----"), 1, 13, sfe569_color_white, sfe569_color_blue );
 }
 
 // ----------------------------------------------------------------------------------------
@@ -242,12 +243,42 @@
 
 // ----------------------------------------------------------------------------------------
 
+#if 0
+static void update_gps_status()
+{
+    if ( ! status_is_gps_up() )
+    {
+        sfe569_write_pgm( PSTR("   offline    "),
+                          6, 10, sfe569_color_white, sfe569_color_red );
+        sfe569_write_pgm( PSTR("              "),
+                          6, 11, sfe569_color_white, sfe569_color_red );
+    }
+    else if ( ! status_does_gps_have_fix() )
+    {
+        sfe569_write_pgm( PSTR("- ---------- -"),
+                          6, 10, sfe569_color_white, sfe569_color_blue );
+        sfe569_write_pgm( PSTR("- ----------  "),
+                          6, 11, sfe569_color_white, sfe569_color_blue );
+    }
+    else
+    {
+        sfe569_write( ' ',  7, 10, sfe569_color_white, sfe569_color_blue );
+        sfe569_write( ' ',  7, 11, sfe569_color_white, sfe569_color_blue );
+        sfe569_write( ' ', 18, 10, sfe569_color_white, sfe569_color_blue );
+        sfe569_write( ' ', 18, 11, sfe569_color_white, sfe569_color_blue );
+    }
+}
+#endif
+
+// ----------------------------------------------------------------------------------------
+
 void display_refresh()
 {
     update_heartbeat_flag();
     update_estop_flag();
     update_wheel_info();
     update_sonar_front_info();
+//    update_gps_status();
 
     ++g_spinner_count;
     g_spinner_count %= 4;
@@ -413,7 +444,7 @@
     format_imu_value( value, buf );
     buf[ 4 ] = '\0';
 
-    sfe569_write( buf, 2, 10, sfe569_color_white, sfe569_color_blue );
+    sfe569_write( buf, 2, 13, sfe569_color_white, sfe569_color_blue );
 }
 
 // ----------------------------------------------------------------------------------------
@@ -427,7 +458,7 @@
     format_imu_value( value, buf );
     buf[ 1 ] = buf[ 0 ];
 
-    sfe569_write( &buf[ 1 ], 8, 10, sfe569_color_white, sfe569_color_blue );
+    sfe569_write( &buf[ 1 ], 8, 13, sfe569_color_white, sfe569_color_blue );
 }
 
 // ----------------------------------------------------------------------------------------
@@ -441,32 +472,122 @@
     format_imu_value( value, buf );
     buf[ 1 ] = buf[ 0 ];
 
-    sfe569_write( &buf[ 1 ], 15, 10, sfe569_color_white, sfe569_color_blue );
+    sfe569_write( &buf[ 1 ], 15, 13, sfe569_color_white, sfe569_color_blue );
 }
 
 // ----------------------------------------------------------------------------------------
-//      1-| Target:+000 (+000)  |-
-
-void display_update_nav_info( int16_t target, int16_t error )
+//  format the GPS value as "123 45.678" in the given buf (11 bytes minimum)
+#if 0
+static void format_gps_value( int16_t degrees, int32_t min_thousandths, char* buf )
 {
-#if 0
-    char buf[ 8 ];
+    char* p = buf + 3; // three digits for degrees followed by space
+    *p-- = ' ';
 
-    // target is displayed as a whole integer, dropping the fractional portion
+    for ( uint8_t i = 0; i < 3; ++i )
+    {
+        int16_t d = degrees % 10;
+        *p-- = ( '0' + d );
+        degrees /= 10;
+    }
+
+    // remove the leading zeros, if any
+
+    if ( buf[ 0 ] == '0' )
+    {
+        buf[ 0 ] = ' ';
 
-    format_imu_value( target, buf );
-    buf[ 4 ] = '\0';
+        if ( buf[ 1 ] == '0' )
+        {
+            buf[ 1 ] = ' ';
+        }
+    }
 
-    sfe569_write( buf, 8, 11, sfe569_color_white, sfe569_color_blue );
+    // now write the factional min_thousandths value
+
+    p = buf + 10; // degrees, space, two digits, decimal point, three digits, null
+    *p-- = '\0';
 
-    // error is displayed as a whole signed value
+    for ( uint8_t i = 0; i < 5; ++i )
+    {
+        int32_t d = min_thousandths % 10;
+        *p-- = ( '0' + d );
+        min_thousandths /= 10;
 
-    format_imu_value( error, buf );
-    buf[ 4 ] = '\0';
+        if ( i == 2 )
+        {
+            *p-- = '.';
+        }
+    }
 
-    sfe569_write( buf, 8, 11, sfe569_color_white, sfe569_color_blue );
-#endif
+    // remove the leading zero, if any
+
+    if ( buf[ 4 ] == '0' )
+    {
+        buf[ 4 ] = ' ';
+    }
 }
 
 // ----------------------------------------------------------------------------------------
 
+void display_update_latitude( int16_t degrees, int32_t min_thousandths )
+{
+    if ( degrees < 0 )
+    {
+        sfe569_write( 'S', 6, 10, sfe569_color_white, sfe569_color_blue );
+        degrees *= -1;
+    }
+    else
+    {
+        sfe569_write( 'N', 6, 10, sfe569_color_white, sfe569_color_blue );
+    }
+
+    char buf[ 12 ];
+    format_gps_value( degrees, min_thousandths, buf );
+    sfe569_write( buf, 8, 10, sfe569_color_white, sfe569_color_blue );
+}
+
+// ----------------------------------------------------------------------------------------
+
+void display_update_longitude( int16_t degrees, int32_t min_thousandths )
+{
+    if ( degrees < 0 )
+    {
+        sfe569_write( 'W', 6, 11, sfe569_color_white, sfe569_color_blue );
+        degrees *= -1;
+    }
+    else
+    {
+        sfe569_write( 'E', 6, 11, sfe569_color_white, sfe569_color_blue );
+    }
+
+    char buf[ 12 ];
+    format_gps_value( degrees, min_thousandths, buf );
+    sfe569_write( buf, 8, 11, sfe569_color_white, sfe569_color_blue );
+}
+
+// ----------------------------------------------------------------------------------------
+
+void display_update_gps_fix( uint8_t satellites )
+{
+    if ( satellites == 0 )
+    {
+        sfe569_write( '-', 19, 10, sfe569_color_white, sfe569_color_blue );
+        sfe569_write( ' ', 19, 11, sfe569_color_white, sfe569_color_blue );
+        return;
+    }
+
+    sfe569_write( '*', 19, 10, sfe569_color_white, sfe569_color_blue );
+
+    if ( satellites < 10 )
+    {
+        sfe569_write( '0' + satellites, 19, 11, sfe569_color_white, sfe569_color_blue );
+    }
+    else
+    {
+        sfe569_write( '*', 19, 11, sfe569_color_white, sfe569_color_blue );
+    }
+}
+#endif 
+
+// ----------------------------------------------------------------------------------------
+
--- a/main/robots/odr-controller/func.h	Sat Jun 28 16:12:28 2014 -0700
+++ b/main/robots/odr-controller/func.h	Sat Jun 28 16:13:53 2014 -0700
@@ -44,6 +44,7 @@
 void status_got_sonar_front_enabled();
 void status_got_sonar_front_disabled();
 void status_got_rf_message( const uint8_t* data, uint8_t length );
+void status_got_gps_fix( bool has_fix );
 
 bool status_is_emergency();
 bool status_is_mgr_up();
@@ -51,6 +52,8 @@
 bool status_is_motion_ctl_alive();
 bool status_is_sonar_front_up();
 bool status_is_sonar_front_enabled();
+bool status_is_gps_up();
+bool status_does_gps_have_fix();
 
 // ----------------------------------------------------------------------------------------
 
@@ -69,6 +72,10 @@
 void display_update_pitch( int32_t value );
 void display_update_roll( int32_t value );
 
+void display_update_latitude( int16_t degrees, int32_t min_thousandths );
+void display_update_longitude( int16_t degrees, int32_t min_thousandths );
+void display_update_gps_fix( uint8_t satellites );
+
 void display_update_button_1( bool down );
 void display_update_button_2( bool down );
 
--- a/main/robots/odr-controller/main.cpp	Sat Jun 28 16:12:28 2014 -0700
+++ b/main/robots/odr-controller/main.cpp	Sat Jun 28 16:13:53 2014 -0700
@@ -361,8 +361,9 @@
         //--    Sleep until there is a new interrupt (the CAN driver gets one, as does
         //      the periodic heartbeat, so we won't sleep forever).
         
-        sleep_mode();
-        
+        //sleep_mode();
+
+#if 1        
         //--    Check for an emergency situation?
 
         if ( g_triggers & trigger_check_emergency )
@@ -406,6 +407,7 @@
                 ++can_comm_errors;
             }
         }
+#endif
 
         //--    Time to refresh the display?
         
@@ -425,6 +427,7 @@
 
         //--    Time to send the heartbeat message?
 
+#if 0
         if ( g_triggers & trigger_send_heartbeat )
         {
             g_triggers &= ~trigger_send_heartbeat;
@@ -434,6 +437,7 @@
                 ++can_comm_errors;
             }
         }
+#endif
 
         //--    Any pending CAN messages to receive/process?
 
@@ -464,11 +468,11 @@
 
         if ( warning )
         {
-            led_yellow_on();
+//            led_yellow_on();
         }
         else
         {
-            led_yellow_off();
+//            led_yellow_off();
         }
     }
 
--- a/main/robots/odr-controller/project_defs.h	Sat Jun 28 16:12:28 2014 -0700
+++ b/main/robots/odr-controller/project_defs.h	Sat Jun 28 16:13:53 2014 -0700
@@ -116,9 +116,9 @@
 // ----------------------------------------------------------------------------------------
 //  packages/common/pololu/qik2s12v10
 
-#define PRJ_POLOLU_QIK2S12V10_ENABLE
-#define PRJ_POLOLU_QIK2S12V10_INPUT_AVR_UART0
-#define PRJ_POLOLU_QIK2S12V10_UART_BAUDRATE    9600
+//#define PRJ_POLOLU_QIK2S12V10_ENABLE
+//#define PRJ_POLOLU_QIK2S12V10_INPUT_AVR_UART0
+//#define PRJ_POLOLU_QIK2S12V10_UART_BAUDRATE    9600
 //#define PRJ_POLOLU_QIK2S12V10_USING_AUTO_BAUDRATE_DETECTION
 
 #endif // #if !defined( PROJECT_DEFS_H )
--- a/main/robots/odr-controller/status.cpp	Sat Jun 28 16:12:28 2014 -0700
+++ b/main/robots/odr-controller/status.cpp	Sat Jun 28 16:13:53 2014 -0700
@@ -43,12 +43,14 @@
 static volatile uint8_t g_beats_without_estop;
 static volatile uint8_t g_beats_without_motion_ctl;
 static volatile uint8_t g_beats_without_sonarfront;
+static volatile uint8_t g_beats_without_gps;
 
 static volatile uint8_t g_status_flags;
 
 static const uint8_t status_flag_emergency_active    = ( 1 << 0 );
 static const uint8_t status_flag_estop_triggered     = ( 1 << 1 );
 static const uint8_t status_flag_sonar_front_enabled = ( 1 << 2 );
+static const uint8_t status_flag_gps_has_fix         = ( 1 << 3 );
 
 // ----------------------------------------------------------------------------------------
 
@@ -74,7 +76,7 @@
     g_beats_without_estop = 100;
     g_beats_without_sonarfront = 100;
     g_beats_without_motion_ctl = 100;
-    // g_beats_without_gps = 100;
+    g_beats_without_gps = 100;
 }
 
 // ----------------------------------------------------------------------------------------
@@ -94,9 +96,9 @@
         case can_node_odr_sonar_front:
             break;
 
-//        case can_node_gps:
-//            g_beats_without_gps = 0;
-//            break;
+        case can_node_sensor_gps:
+            g_beats_without_gps = 0;
+            break;
     }
 }
 
@@ -154,6 +156,7 @@
     if ( g_beats_without_estop < 200 ) { ++g_beats_without_estop; }
     if ( g_beats_without_sonarfront < 200 ) { ++g_beats_without_sonarfront; }
     if ( g_beats_without_motion_ctl < 200 ) { ++g_beats_without_motion_ctl; }
+    if ( g_beats_without_gps < 200 ) { ++g_beats_without_gps; }
 
     //--    Has the ESTOP condition transitioned? Note we ignore this check if the
     //      override switch has been set.
@@ -167,7 +170,7 @@
 
         if ( status_is_emergency() )
         {
-            canmsg_send_all_clear();
+//            canmsg_send_all_clear();
         }
     }
     else if ( g_status_flags & status_flag_estop_triggered )
@@ -180,12 +183,12 @@
         {
             // switching from ESTOP triggered to not triggered
             g_status_flags &= ~status_flag_estop_triggered;
-            canmsg_send_all_clear();
+//            canmsg_send_all_clear();
         }
         else
         {
             // keep sending the emergency until the ESTOP is not triggered
-            canmsg_send_emergency();
+//            canmsg_send_emergency();
         }
     }
     else
@@ -198,12 +201,12 @@
         {
             // switching from ESTOP not triggered to triggered
             g_status_flags |= status_flag_estop_triggered;
-            canmsg_send_emergency();
+//            canmsg_send_emergency();
         }
         else if ( status_is_emergency() )
         {
             // keep sending the all-clear until we are out of the emergency state
-            canmsg_send_all_clear();
+//            canmsg_send_all_clear();
         }
     }
 }
@@ -296,3 +299,33 @@
 
 // ----------------------------------------------------------------------------------------
 
+bool status_is_gps_up()
+{
+    // more than four seconds == down
+    return ( g_beats_without_gps < 8 );
+}
+
+// ----------------------------------------------------------------------------------------
+
+bool status_does_gps_have_fix()
+{
+    // more than four seconds == down
+    return ( g_status_flags & status_flag_gps_has_fix ) > 0;
+}
+
+// ----------------------------------------------------------------------------------------
+
+void status_got_gps_fix( bool has_fix )
+{
+    if ( has_fix )
+    {
+        g_status_flags |= status_flag_gps_has_fix;
+    }
+    else
+    {
+        g_status_flags &= ~status_flag_gps_has_fix;
+    }
+}
+
+// ----------------------------------------------------------------------------------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/robots/odr-display-4d/bcui.inc	Sat Jun 28 16:13:53 2014 -0700
@@ -0,0 +1,467 @@
+// ----------------------------------------------------------------------------------------
+//
+//  Copyright (C) 2014 Bob Cook
+//
+//  Bob Cook Development, Robotics Library
+//  http://www.bobcookdev.com/rl/
+//
+//  bcui is a simple UI widget framework for the 4D Systems Picaso LCD display products.
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+//
+// ----------------------------------------------------------------------------------------
+
+#constant BCUI_WIDGET_TYPE_BUTTON := 0
+#constant BCUI_WIDGET_TYPE_ARROW  := 1
+
+#constant BCUI_WIDGET_ARROW_LEFT  := 0
+#constant BCUI_WIDGET_ARROW_UP    := 1
+#constant BCUI_WIDGET_ARROW_RIGHT := 2
+#constant BCUI_WIDGET_ARROW_DOWN  := 3
+
+// ----------------------------------------------------------------------------------------
+
+#constant BCUI_VIEW_INFO_SIZE := ( 1 + 1 + 1 ) // for the fields below
+#constant BCUI_VIEW_INFO_OFFSET_LINK    := 0
+#constant BCUI_VIEW_INFO_OFFSET_WIDGETS := 1
+#constant BCUI_VIEW_INFO_OFFSET_COLOR   := 2
+
+#constant BCUI_WIDGET_INFO_SIZE := ( 1 + 1 + 1 + 2 + 4 + 1 + 1 ) // for the fields below
+#constant BCUI_WIDGET_INFO_OFFSET_LINK  := 0
+#constant BCUI_WIDGET_INFO_OFFSET_OWNER := 1
+#constant BCUI_WIDGET_INFO_OFFSET_TYPE  := 2
+#constant BCUI_WIDGET_INFO_OFFSET_X     := 3
+#constant BCUI_WIDGET_INFO_OFFSET_Y     := 4
+#constant BCUI_WIDGET_INFO_OFFSET_RECT  := 5 // x1, y1, x2, y2
+#constant BCUI_WIDGET_INFO_OFFSET_COLOR := 9
+#constant BCUI_WIDGET_INFO_OFFSET_LABEL := 10
+
+#constant BCUI_WIDGET_BUTTON_HEIGHT := 26
+#constant BCUI_WIDGET_BUTTON_WIDTH  := 70
+#constant BCUI_WIDGET_BUTTON_CORNER :=  8
+
+#constant BCUI_WIDGET_ARROW_DIAMETER := 32
+#constant BCUI_WIDGET_ARROW_OFFSET_1 :=  8
+#constant BCUI_WIDGET_ARROW_OFFSET_2 := 10
+
+// ----------------------------------------------------------------------------------------
+
+var bcui_view_list; // linked list of view info structures
+var bcui_current_view; // active_view or 0 if no active view
+
+// ----------------------------------------------------------------------------------------
+
+// these are the built-in monospaced fonts, and are much faster to render
+#constant BCUI_MONO_FONT_LG := FONT3
+#constant BCUI_MONO_FONT_SM := FONT1
+
+// these are custom fonts, loaded from external storage
+var BCUI_TITLE_FONT_LG;
+var BCUI_TITLE_FONT_SM;
+
+// ----------------------------------------------------------------------------------------
+
+func bcui_init()
+
+    bcui_view_list    := 0;
+    bcui_current_view := 0;
+
+    if ( file_Mount() != 0 )
+        BCUI_TITLE_FONT_LG := file_LoadImageControl( "tahoma15.da1", "tahoma15.gc1", 1 );
+    endif
+
+endfunc
+
+// ----------------------------------------------------------------------------------------
+
+func bcui_widget_is_touchable( var widget )
+
+    switch ( widget[ BCUI_WIDGET_INFO_OFFSET_TYPE ] )
+
+        case BCUI_WIDGET_TYPE_BUTTON:
+        case BCUI_WIDGET_TYPE_ARROW:
+            return TRUE;
+
+    endswitch
+
+    return FALSE;
+
+endfunc
+
+// ----------------------------------------------------------------------------------------
+
+func bcui_pt_in_widget( var x, var y, var widget )
+    if ( x >= widget[ BCUI_WIDGET_INFO_OFFSET_RECT + 0 ]
+      && x <= widget[ BCUI_WIDGET_INFO_OFFSET_RECT + 2 ]
+      && y >= widget[ BCUI_WIDGET_INFO_OFFSET_RECT + 1 ]
+      && y <= widget[ BCUI_WIDGET_INFO_OFFSET_RECT + 3 ] )
+        return ( TRUE );
+    else
+        return ( FALSE );
+    endif
+endfunc
+
+// ----------------------------------------------------------------------------------------
+
+func bcui_darken( var color )
+    return ( ( ( color >> 12 ) & 0x0f ) << 11 )
+           | ( ( ( color >> 6 ) & 0x1f ) << 5 )
+       | ( ( color >> 1 ) & 0x0f );
+endfunc
+
+// ----------------------------------------------------------------------------------------
+
+func bcui_create_view( var background )
+
+    var private view;
+
+    view := mem_AllocZ( BCUI_VIEW_INFO_SIZE * 2 ); // 2x for bytes from words
+    view[ BCUI_VIEW_INFO_OFFSET_LINK ] := bcui_view_list;
+    bcui_view_list := view;
+
+    view[ BCUI_VIEW_INFO_OFFSET_WIDGETS ] := 0;
+    view[ BCUI_VIEW_INFO_OFFSET_COLOR ]   := background;
+
+    return view;
+
+endfunc
+
+// ----------------------------------------------------------------------------------------
+
+func bcui_create_button( var view, var center_x, var center_y, var label, var color )
+
+    var private button;
+
+    button := mem_AllocZ( BCUI_WIDGET_INFO_SIZE * 2 ); // 2x for bytes from words
+    button[ BCUI_WIDGET_INFO_OFFSET_LINK ] := view[ BCUI_VIEW_INFO_OFFSET_WIDGETS ];
+    view[ BCUI_VIEW_INFO_OFFSET_WIDGETS ]  := button;
+
+    button[ BCUI_WIDGET_INFO_OFFSET_OWNER ] := view;
+    button[ BCUI_WIDGET_INFO_OFFSET_TYPE ]  := BCUI_WIDGET_TYPE_BUTTON;
+
+    button[ BCUI_WIDGET_INFO_OFFSET_RECT + 0 ] := center_x - ( BCUI_WIDGET_BUTTON_WIDTH  / 2 );
+    button[ BCUI_WIDGET_INFO_OFFSET_RECT + 1 ] := center_y - ( BCUI_WIDGET_BUTTON_HEIGHT / 2 );
+    button[ BCUI_WIDGET_INFO_OFFSET_RECT + 2 ] := center_x + ( BCUI_WIDGET_BUTTON_WIDTH  / 2 );
+    button[ BCUI_WIDGET_INFO_OFFSET_RECT + 3 ] := center_y + ( BCUI_WIDGET_BUTTON_HEIGHT / 2 );
+
+    button[ BCUI_WIDGET_INFO_OFFSET_X ] := center_x;
+    button[ BCUI_WIDGET_INFO_OFFSET_Y ] := center_y;
+
+    button[ BCUI_WIDGET_INFO_OFFSET_COLOR ] := color;
+
+    button[ BCUI_WIDGET_INFO_OFFSET_LABEL ] := mem_AllocZ( str_Length( label ) + 1 );
+    str_Copy( str_Ptr( button[ BCUI_WIDGET_INFO_OFFSET_LABEL ] ), label );
+
+    return button;
+
+endfunc
+
+// ----------------------------------------------------------------------------------------
+
+func bcui_create_arrow( var view, var center_x, var center_y, var direction, var color )
+
+    var private arrow;
+
+    arrow := mem_AllocZ( BCUI_WIDGET_INFO_SIZE * 2 ); // 2x for bytes from words
+    arrow[ BCUI_WIDGET_INFO_OFFSET_LINK ] := view[ BCUI_VIEW_INFO_OFFSET_WIDGETS ];
+    view[ BCUI_VIEW_INFO_OFFSET_WIDGETS ] := arrow;
+
+    arrow[ BCUI_WIDGET_INFO_OFFSET_OWNER ] := view;
+    arrow[ BCUI_WIDGET_INFO_OFFSET_TYPE ]  := BCUI_WIDGET_TYPE_ARROW;
+
+    arrow[ BCUI_WIDGET_INFO_OFFSET_RECT + 0 ] := center_x - ( BCUI_WIDGET_ARROW_DIAMETER / 2 );
+    arrow[ BCUI_WIDGET_INFO_OFFSET_RECT + 1 ] := center_y - ( BCUI_WIDGET_ARROW_DIAMETER / 2 );
+    arrow[ BCUI_WIDGET_INFO_OFFSET_RECT + 2 ] := center_x + ( BCUI_WIDGET_ARROW_DIAMETER / 2 );
+    arrow[ BCUI_WIDGET_INFO_OFFSET_RECT + 3 ] := center_y + ( BCUI_WIDGET_ARROW_DIAMETER / 2 );
+
+    arrow[ BCUI_WIDGET_INFO_OFFSET_X ] := center_x;
+    arrow[ BCUI_WIDGET_INFO_OFFSET_Y ] := center_y;
+
+    arrow[ BCUI_WIDGET_INFO_OFFSET_LABEL ] := direction; // left, up, right, down
+
+    arrow[ BCUI_WIDGET_INFO_OFFSET_COLOR ] := color;
+
+    return arrow;
+
+endfunc
+
+// ----------------------------------------------------------------------------------------
+
+func bcui_do_touch() // returns object or 0 if no hit
+
+    if ( bcui_current_view == 0 )
+        return 0;
+    endif
+
+    var private widget;
+
+    widget := bcui_current_view[ BCUI_VIEW_INFO_OFFSET_WIDGETS ];
+
+    while ( widget != 0 )
+
+        var private touch_x, touch_y;
+
+        touch_x := touch_Get( TOUCH_GETX );
+        touch_y := touch_Get( TOUCH_GETY );
+
+        if ( bcui_widget_is_touchable( widget ) && bcui_pt_in_widget( touch_x, touch_y, widget ) )
+
+            bcui_paint_widget( widget, TRUE /* touched */ );
+
+            while ( touch_Get( TOUCH_STATUS ) != TOUCH_RELEASED )
+                pause( 10 );
+            wend
+
+            bcui_paint_widget( widget, FALSE /* not touched */ );
+            return widget;
+
+        endif
+
+        widget := widget[ BCUI_WIDGET_INFO_OFFSET_LINK ];
+
+    wend
+
+    // no match
+    return 0;
+
+endfunc
+
+// ----------------------------------------------------------------------------------------
+
+func bcui_activate_view( var view )
+
+    bcui_current_view := view;
+
+    if ( bcui_current_view == 0 )
+        gfx_RectangleFilled( 0, 0, gfx_Get( X_MAX ), gfx_Get( Y_MAX ), GREEN );
+        return;
+    endif
+
+    gfx_RectangleFilled( 0, 0,
+                         gfx_Get( X_MAX ), gfx_Get( Y_MAX ),
+                         view[ BCUI_VIEW_INFO_OFFSET_COLOR ] );
+
+    var private widget;
+
+    widget := view[ BCUI_VIEW_INFO_OFFSET_WIDGETS ];
+
+    while ( widget != 0 )
+
+        bcui_paint_widget( widget, FALSE /* not touched */ );
+
+        widget := widget[ BCUI_WIDGET_INFO_OFFSET_LINK ];
+
+    wend
+
+endfunc
+
+// ----------------------------------------------------------------------------------------
+
+func bcui_paint_widget( var widget, var as_touched )
+
+    var private widget_color;
+
+    if ( as_touched )
+        widget_color := bcui_darken( widget[ BCUI_WIDGET_INFO_OFFSET_COLOR ] );
+    else
+        widget_color := widget[ BCUI_WIDGET_INFO_OFFSET_COLOR ];
+    endif
+
+    switch ( widget[ BCUI_WIDGET_INFO_OFFSET_TYPE ] )
+
+        case BCUI_WIDGET_TYPE_BUTTON:
+
+            bcui_paint_button( widget[ BCUI_WIDGET_INFO_OFFSET_X ],
+                               widget[ BCUI_WIDGET_INFO_OFFSET_Y ],
+                               widget_color,
+                               widget[ BCUI_WIDGET_INFO_OFFSET_LABEL ] );
+            break;
+
+        case BCUI_WIDGET_TYPE_ARROW:
+
+            bcui_paint_arrow( widget[ BCUI_WIDGET_INFO_OFFSET_X ],
+                              widget[ BCUI_WIDGET_INFO_OFFSET_Y ],
+                              widget_color,
+                              widget[ BCUI_WIDGET_INFO_OFFSET_LABEL ] );
+            break;
+
+    endswitch
+
+endfunc
+
+// ----------------------------------------------------------------------------------------
+
+func bcui_paint_button( var center_x, var center_y, var color, var label )
+
+    // paint the four corners
+    gfx_CircleFilled( center_x - ( ( BCUI_WIDGET_BUTTON_WIDTH  / 2 ) - BCUI_WIDGET_BUTTON_CORNER ),
+                      center_y - ( ( BCUI_WIDGET_BUTTON_HEIGHT / 2 ) - BCUI_WIDGET_BUTTON_CORNER ),
+                      BCUI_WIDGET_BUTTON_CORNER, color );
+    gfx_CircleFilled( center_x - ( ( BCUI_WIDGET_BUTTON_WIDTH  / 2 ) - BCUI_WIDGET_BUTTON_CORNER ),
+                      center_y + ( ( BCUI_WIDGET_BUTTON_HEIGHT / 2 ) - BCUI_WIDGET_BUTTON_CORNER ),
+                      BCUI_WIDGET_BUTTON_CORNER, color );
+    gfx_CircleFilled( center_x + ( ( BCUI_WIDGET_BUTTON_WIDTH  / 2 ) - BCUI_WIDGET_BUTTON_CORNER ),
+                      center_y - ( ( BCUI_WIDGET_BUTTON_HEIGHT / 2 ) - BCUI_WIDGET_BUTTON_CORNER ),
+                      BCUI_WIDGET_BUTTON_CORNER, color );
+    gfx_CircleFilled( center_x + ( ( BCUI_WIDGET_BUTTON_WIDTH  / 2 ) - BCUI_WIDGET_BUTTON_CORNER ),
+                      center_y + ( ( BCUI_WIDGET_BUTTON_HEIGHT / 2 ) - BCUI_WIDGET_BUTTON_CORNER ),
+                      BCUI_WIDGET_BUTTON_CORNER, color );
+
+    // paint the filled rectanges (center, left, right )
+    gfx_RectangleFilled( center_x - ( ( BCUI_WIDGET_BUTTON_WIDTH / 2 ) - BCUI_WIDGET_BUTTON_CORNER ),
+                         center_y - ( BCUI_WIDGET_BUTTON_HEIGHT / 2 ),
+                         center_x + ( ( BCUI_WIDGET_BUTTON_WIDTH / 2 ) - BCUI_WIDGET_BUTTON_CORNER ),
+                         center_y + ( BCUI_WIDGET_BUTTON_HEIGHT / 2 ),
+                         color );
+    gfx_RectangleFilled( center_x - ( BCUI_WIDGET_BUTTON_WIDTH / 2 ),
+                         center_y - ( ( BCUI_WIDGET_BUTTON_HEIGHT / 2 ) - BCUI_WIDGET_BUTTON_CORNER ),
+                         center_x - ( ( BCUI_WIDGET_BUTTON_WIDTH / 2 ) - BCUI_WIDGET_BUTTON_CORNER ),
+                         center_y + ( ( BCUI_WIDGET_BUTTON_HEIGHT / 2 ) - BCUI_WIDGET_BUTTON_CORNER ),
+                         color );
+    gfx_RectangleFilled( center_x + ( ( BCUI_WIDGET_BUTTON_WIDTH  / 2 ) - BCUI_WIDGET_BUTTON_CORNER ),
+                         center_y - ( ( BCUI_WIDGET_BUTTON_HEIGHT / 2 ) - BCUI_WIDGET_BUTTON_CORNER ),
+                         center_x + ( BCUI_WIDGET_BUTTON_WIDTH / 2 ),
+                         center_y + ( ( BCUI_WIDGET_BUTTON_HEIGHT / 2 ) - BCUI_WIDGET_BUTTON_CORNER ),
+                         color );
+
+    // paint the label
+    txt_Set( FONT_ID, BCUI_TITLE_FONT_LG );
+    txt_Set( TEXT_COLOUR, WHITE );
+    txt_Set( TEXT_HIGHLIGHT, color );
+    var private w, h;
+    w := strwidth( label );
+    h := strheight();
+    gfx_MoveTo( center_x - ( w / 2 ), center_y - ( h / 2 ) );
+    putstr( label );
+
+endfunc
+
+// ----------------------------------------------------------------------------------------
+
+func bcui_paint_arrow( var center_x, var center_y, var color, var direction )
+
+    // paint the circle itself
+    gfx_CircleFilled( center_x, center_y, ( BCUI_WIDGET_ARROW_DIAMETER / 2 ), color );
+
+    // paint the arrow inside, pointing in the right direction
+    switch ( direction )
+
+        case BCUI_WIDGET_ARROW_LEFT:
+
+            gfx_Triangle( ( center_x - BCUI_WIDGET_ARROW_OFFSET_2 ),
+                          center_y,
+                          ( center_x + ( BCUI_WIDGET_ARROW_OFFSET_2 / 2 ) ),
+                          ( center_y - BCUI_WIDGET_ARROW_OFFSET_1 ),
+                          ( center_x + ( BCUI_WIDGET_ARROW_OFFSET_2 / 2 ) ),
+                          ( center_y + BCUI_WIDGET_ARROW_OFFSET_1 ),
+                          WHITE );
+            break;
+
+        case BCUI_WIDGET_ARROW_UP:
+
+            gfx_Triangle( center_x,
+                          ( center_y - BCUI_WIDGET_ARROW_OFFSET_2 ),
+                          ( center_x - BCUI_WIDGET_ARROW_OFFSET_1 ),
+                          ( center_y + ( BCUI_WIDGET_ARROW_OFFSET_2 / 2 ) ),
+                          ( center_x + BCUI_WIDGET_ARROW_OFFSET_1 ),
+                          ( center_y + ( BCUI_WIDGET_ARROW_OFFSET_2 / 2 ) ),
+                          WHITE );
+            break;
+
+        case BCUI_WIDGET_ARROW_RIGHT:
+
+            gfx_Triangle( ( center_x + BCUI_WIDGET_ARROW_OFFSET_2 ),
+                          center_y,
+                          ( center_x - ( BCUI_WIDGET_ARROW_OFFSET_2 / 2 ) ),
+                          ( center_y - BCUI_WIDGET_ARROW_OFFSET_1 ),
+                          ( center_x - ( BCUI_WIDGET_ARROW_OFFSET_2 / 2 ) ),
+                          ( center_y + BCUI_WIDGET_ARROW_OFFSET_1 ),
+                          WHITE );
+            break;
+
+        case BCUI_WIDGET_ARROW_DOWN:
+
+            gfx_Triangle( center_x,
+                          ( center_y + BCUI_WIDGET_ARROW_OFFSET_2 ),
+                          ( center_x - BCUI_WIDGET_ARROW_OFFSET_1 ),
+                          ( center_y - ( BCUI_WIDGET_ARROW_OFFSET_2 / 2 ) ),
+                          ( center_x + BCUI_WIDGET_ARROW_OFFSET_1 ),
+                          ( center_y - ( BCUI_WIDGET_ARROW_OFFSET_2 / 2 ) ),
+                          WHITE );
+            break;
+
+    endswitch
+
+endfunc
+
+// ----------------------------------------------------------------------------------------
+
+func bcui_text_left( var text, var x, var y, var font, var color, var bkgnd )
+
+    // compute the metrics of the resulting string
+    txt_Set( FONT_ID, font );
+    txt_Set( TEXT_COLOUR, color );
+    txt_Set( TEXT_HIGHLIGHT, bkgnd );
+    var private w, h;
+    w := strwidth( text );
+    h := strheight();
+    // move to the right x,y location for left alignment
+    gfx_MoveTo( x, y - ( h / 2 ) );
+    // and paint the string
+    putstr( text );
+
+endfunc
+
+// ----------------------------------------------------------------------------------------
+
+func bcui_text_center( var text, var x, var y, var font, var color, var bkgnd )
+
+    // compute the metrics of the resulting string
+    txt_Set( FONT_ID, font );
+    txt_Set( TEXT_COLOUR, color );
+    txt_Set( TEXT_HIGHLIGHT, bkgnd );
+    var private w, h;
+    w := strwidth( text );
+    h := strheight();
+    // move to the right x,y location for center alignment
+    gfx_MoveTo( x - ( w / 2 ), y - ( h / 2 ) );
+    // and paint the string
+    putstr( text );
+
+endfunc
+
+// ----------------------------------------------------------------------------------------
+
+func bcui_text_right( var text, var x, var y, var font, var color, var bkgnd )
+
+    // compute the metrics of the resulting string
+    txt_Set( FONT_ID, font );
+    txt_Set( TEXT_COLOUR, color );
+    txt_Set( TEXT_HIGHLIGHT, bkgnd );
+    var private w, h;
+    w := strwidth( text );
+    h := strheight();
+    // move to the right x,y location for right alignment
+    gfx_MoveTo( x - w, y - ( h / 2 ) );
+    // and paint the string
+    putstr( text );
+
+endfunc
+
+// ----------------------------------------------------------------------------------------
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/robots/odr-display-4d/odr-display-4d.4dg	Sat Jun 28 16:13:53 2014 -0700
@@ -0,0 +1,316 @@
+#platform "uLCD-32PTU"
+
+#inherit "4DGL_16bitColours.fnc"
+
+#constant FALSE := 0
+#constant TRUE  := 1
+
+#inherit "bcui.inc"
+
+var disk, hFont1;
+
+#constant COMM_COMMAND_MARKER := '|'
+
+#constant COMM_STATE_GET_MARKER  := 0
+#constant COMM_STATE_GET_COMMAND := 1
+#constant COMM_STATE_GET_MESSAGE := 2
+
+#constant COMM_MAX_MESSAGE_SIZE := 12
+#constant COMM_RX_BUFFER_SIZE   := 64
+#constant COMM_TX_BUFFER_SIZE   := 12
+
+var comm_func_cmd_msg_size;
+var comm_func_cmd_handler;
+
+var comm_errors;
+
+var comm_state;
+var comm_command;
+var comm_msgsize;
+var comm_msgrecv;
+var comm_message[ COMM_MAX_MESSAGE_SIZE ];
+
+var comm_rx_buffer[ COMM_RX_BUFFER_SIZE ];
+var comm_tx_buffer[ COMM_TX_BUFFER_SIZE ];
+
+func comm_init( var func_cmd_msg_size, var func_cmd_handler )
+
+    comm_func_cmd_msg_size := func_cmd_msg_size;
+    comm_func_cmd_handler  := func_cmd_handler;
+
+    com_Init( comm_rx_buffer, COMM_RX_BUFFER_SIZE * 2 /* 2x for words to bytes */, 0 /* no qualifier */ );
+    setbaud( BAUD_115200 );
+
+    com_TXbuffer( comm_tx_buffer, COMM_TX_BUFFER_SIZE * 2 /* 2x for words to bytes */, 0 /* no pin */ );
+
+endfunc
+
+func comm_read()
+
+    // did the receive buffer overflow? reset if so
+    if ( com_Full() && com_Count() == 0 )
+        com_Reset();
+        com_Init( comm_rx_buffer, COMM_RX_BUFFER_SIZE * 2 /* 2x for words to bytes */, 0 /* no qualifier */ );
+        setbaud( BAUD_115200 );
+        comm_state := COMM_STATE_GET_MARKER;
+        comm_errors++;
+    endif
+
+    var private msg_ptr, value;
+    msg_ptr := str_Ptr( comm_message );
+
+    while ( com_Count() != 0 )
+
+        value := serin();
+
+        switch ( comm_state )
+
+            case COMM_STATE_GET_MARKER:
+                if ( value == COMM_COMMAND_MARKER )
+                    comm_state := COMM_STATE_GET_COMMAND;
+                endif
+                break;
+
+            case COMM_STATE_GET_COMMAND:
+                comm_command := value;
+                comm_msgsize := comm_func_cmd_msg_size( comm_command );
+                comm_msgrecv := 0;
+                comm_state   := COMM_STATE_GET_MESSAGE;
+                break;
+
+            case COMM_STATE_GET_MESSAGE:
+                str_PutByte( msg_ptr + comm_msgrecv, value );
+                comm_msgrecv++;
+                break;
+
+        endswitch
+
+        // have a complete command?
+        if ( comm_state == COMM_STATE_GET_MESSAGE )
+            if ( comm_msgsize == comm_msgrecv )
+                str_PutByte( msg_ptr + comm_msgrecv, 0 ); // null termniator
+                comm_func_cmd_handler( comm_command, comm_message);
+                comm_state := COMM_STATE_GET_MARKER;
+            endif
+        endif
+
+    wend
+
+endfunc
+
+#constant ODRD_FONT  := FONT1
+#constant ODRD_BKGND := 0x4208
+
+func odrd_get_cmd_msg_size( var command )
+
+    switch ( command )
+
+        case 'F': // motor speed set, front, sign and 2 digits
+        case 'f': // motor speed actual, front, sign and 2 digits
+            return 3;
+
+        case 'R': // motor speed set, rear, sign and 2 digits
+        case 'r': // motor speed actual, rear, sign and 2 digits
+            return 3;
+
+    endswitch
+
+    return 0; // unknown command, no message bytes
+
+endfunc
+
+func odrd_do_command( var command, var message )
+
+    switch ( command )
+
+        case 'F': // motor speed set, front
+            bcui_text_center( message, 185, 150, ODRD_FONT, WHITE, ODRD_BKGND );
+            break;
+
+        case 'f': // motor speed actual, front
+            bcui_text_left( message, 275, 200, ODRD_FONT, WHITE, ODRD_BKGND );
+            break;
+
+    endswitch
+
+endfunc
+
+func main()
+
+    gfx_ScreenMode( LANDSCAPE );
+    touch_Set( TOUCH_ENABLE );
+
+    bcui_init();
+
+    //var hFont1;
+    disk   := file_Mount();
+//    hFont1 := file_LoadImageControl("AddFonts.da1", "AddFonts.gc1", 1);
+    hFont1 := file_LoadImageControl( "tahoma15.da1", "tahoma15.gc1", 1 );
+
+
+    //gfx_RectangleFilled( 0, 0, 319, 239, WHITE );
+
+    //txt_FGcolour( WHITE );
+    //gfx_MoveTo( 10, 10 );
+    //print( "Hello World" );
+
+    var private theView, btn_Hello, btn_There;
+
+    theView := bcui_create_view( ODRD_BKGND );
+
+    //btn_Hello := bcui_create_button( theView, 50, 100, "Hello", BLUEVIOLET );
+    //btn_There := bcui_create_button( theView, 50, 150, "There", CORNFLOWERBLUE );
+    bcui_create_button( theView, 50, 200, "One", BLUE );
+
+    //bcui_create_arrow( theView, 150, 150, BCUI_WIDGET_ARROW_LEFT, GREEN );
+    //bcui_create_arrow( theView, 220, 150, BCUI_WIDGET_ARROW_RIGHT, GREEN );
+    //bcui_create_arrow( theView, 185, 115, BCUI_WIDGET_ARROW_UP, GREEN );
+    //bcui_create_arrow( theView, 185, 185, BCUI_WIDGET_ARROW_DOWN, GREEN );
+
+    bcui_activate_view( theView );
+
+    //bcui_text_right( "Actual:", 270, 200, ODRD_FONT, WHITE, ODRD_BKGND );
+    //bcui_text_left( "000", 275, 200, ODRD_FONT, WHITE, ODRD_BKGND );
+
+    //bcui_text_center( "---", 185, 150, ODRD_FONT, WHITE, ODRD_BKGND );
+
+    //gfx_RectangleFilled( 50, 50, 100, 70, GREEN );
+    //gfx_RectangleFilled( 120, 50, 140, 70, RED );
+
+    comm_init( odrd_get_cmd_msg_size, odrd_do_command );
+
+    // FONT1: 14 rows, 40 columns, leaves room top/bottom for buttons
+    var private text_indent, text_height;
+    var private i;
+    /*
+    text_indent := strwidth( "0123456789012345678901234567890123456789" );
+    text_height := strheight();
+    text_indent := ( ( gfx_Get( X_MAX ) - text_indent ) / 2 );
+    text_height := text_height + 2; //* 3 / 2;
+    gfx_MoveTo( text_indent, 20 );
+    putstr( "abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMN" );
+//    putstr( "01234567890123456789012345678901234567890" );
+    gfx_MoveTo( text_indent, 20 + text_height );
+    putstr( "abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMN" );
+    gfx_MoveTo( 10, 45 );
+    putnum( DEC, text_height );
+    for ( i := 0; i < 14; i++ )
+        gfx_MoveTo( 250, 20 + ( i * text_height ) );
+        putnum( DEC, i );
+    next
+    */
+
+    // FONT3: 12 rows, 35 columns, leaves room top/bottom for buttons
+    //txt_Set( FONT_ID, FONT3 );
+    //text_indent := strwidth( "01234567890123456789012345678901234" );
+    //text_height := strheight();
+    //text_indent := ( ( gfx_Get( X_MAX ) - text_indent ) / 2 );
+    //text_height := text_height + 2;
+    /*
+    gfx_MoveTo( text_indent, 20 );
+    putstr( "01234567890123456789012345678901234" );
+    gfx_MoveTo( text_indent, 20 + text_height );
+    putstr( "abcdefghijklmnopqrstuvwzyzABCDEFGHI" );
+    gfx_MoveTo( 10, 60 );
+    putnum( DEC, text_height );
+    for ( i := 0; i < 12; i++ )
+        if ( i > 1 )
+            gfx_MoveTo( 250, 20 + ( i * text_height ) );
+            putnum( DEC, i );
+        endif
+    next
+    */
+
+    // box some rows of text
+    //txt_Set( FONT_ID, FONT1 );
+    //text_indent := strwidth( "01234567890123456789012345678901234" );
+    //text_height := strheight();
+    //text_indent := ( ( gfx_Get( X_MAX ) - text_indent ) / 2 );
+    //text_height := text_height + 2;
+    //gfx_MoveTo( 150, 20 );
+    //var private box_width;
+    //box_width := 20 + 3 * charwidth( '0' );
+    //gfx_BoxTo( 150 + box_width, 20 + 10 + 3 * ( 4 + text_height ) );
+    //gfx_MoveTo( 150 + 5, 20 + 1 * ( 0 + text_height ) );
+    //putstr( "000" );
+    //gfx_MoveTo( 150 + 5, 20 + 2 * ( 0 + text_height ) );
+    //putstr( "111" );
+    //bcui_text_center( "XyZ", 150 + ( box_width / 2 ), 20 + 5 + 1 * ( 4 + text_height ) - ( text_height / 2 ) - 2, FONT3, WHITE, ODRD_BKGND );
+    //bcui_text_center( "123", 150 + ( box_width / 2 ), 20 + 5 + 2 * ( 4 + text_height ) - ( text_height / 2 ) - 2, FONT3, WHITE, ODRD_BKGND );
+    //bcui_text_center( "ABC", 150 + ( box_width / 2 ), 20 + 5 + 3 * ( 4 + text_height ) - ( text_height / 2 ) - 2, FONT3, WHITE, ODRD_BKGND );
+
+    //bcui_text_right( "Front Speed:", 200, 20 + 1 * ( 2 + text_height ) - ( text_height / 2 ) - 1, FONT1, WHITE, ODRD_BKGND );
+    //bcui_text_right( "Rear Speed:",  200, 20 + 2 * ( 2 + text_height ) - ( text_height / 2 ) - 1, FONT1, WHITE, ODRD_BKGND );
+
+    //bcui_text_left( "000", 210, 20 + 1 * ( 2 + text_height ) - ( text_height / 2 ) - 1, FONT1, WHITE, ODRD_BKGND );
+    //bcui_text_left( "+12", 210, 20 + 2 * ( 2 + text_height ) - ( text_height / 2 ) - 1, FONT1, WHITE, ODRD_BKGND );
+
+    bcui_text_right( "Speed", 65, 50, BCUI_TITLE_FONT_LG, WHITE, ODRD_BKGND );
+    bcui_text_center( "Front", 100, 30, BCUI_TITLE_FONT_LG, WHITE, ODRD_BKGND );
+    bcui_text_center( "000", 100, 50, BCUI_MONO_FONT_LG, WHITE, ODRD_BKGND );
+    bcui_text_center( "000", 100, 62, BCUI_MONO_FONT_SM, GRAY, ODRD_BKGND );
+    bcui_text_center( "Rear", 150, 30, BCUI_TITLE_FONT_LG, WHITE, ODRD_BKGND );
+    bcui_text_center( "000", 150, 50, BCUI_MONO_FONT_LG, WHITE, ODRD_BKGND );
+    bcui_text_center( "000", 150, 62, BCUI_MONO_FONT_SM, GRAY, ODRD_BKGND );
+
+    bcui_text_right( "Steer", 65, 85, BCUI_TITLE_FONT_LG, WHITE, ODRD_BKGND );
+//    bcui_text_center( "000", 100, 85, BCUI_MONO_FONT_LG, WHITE, ODRD_BKGND );
+//    bcui_text_center( "000", 150, 85, BCUI_MONO_FONT_LG, WHITE, ODRD_BKGND );
+    bcui_text_center( "???", 100, 85, BCUI_MONO_FONT_LG, RED, ODRD_BKGND );
+    bcui_text_center( "???", 150, 85, BCUI_MONO_FONT_LG, RED, ODRD_BKGND );
+
+    gfx_Set( OBJECT_COLOUR, BLUE );
+    gfx_MoveTo( 75, 20 );
+    gfx_LineTo( 75, 95 );
+    gfx_MoveTo( 125, 20 );
+    gfx_LineTo( 125, 95 );
+    gfx_MoveTo( 175, 20 );
+    gfx_LineTo( 175, 95 );
+
+    bcui_text_right( "Location", 65, 120, BCUI_TITLE_FONT_LG, WHITE, ODRD_BKGND );
+    bcui_text_left( "N 123 45.678  W 123 45.678", 85, 120, BCUI_MONO_FONT_LG, WHITE, ODRD_BKGND );
+    bcui_text_right( "Satellites", 65, 135, BCUI_TITLE_FONT_LG, GRAY, ODRD_BKGND );
+    bcui_text_left( "02", 85, 135, BCUI_MONO_FONT_SM, GRAY, ODRD_BKGND );
+
+    bcui_text_right( "Waypoint", 65, 160, BCUI_TITLE_FONT_LG, WHITE, ODRD_BKGND );
+    bcui_text_left( "N 123 45.678  W 123 45.678", 85, 160, BCUI_MONO_FONT_LG, WHITE, ODRD_BKGND );
+    bcui_text_right( "Distance", 65, 175, BCUI_TITLE_FONT_LG, WHITE, ODRD_BKGND );
+    bcui_text_left( "25", 85, 175, BCUI_MONO_FONT_LG, GRAY, ODRD_BKGND );
+
+    var private x, y;
+    repeat
+
+        comm_read();
+
+        txt_Set( TEXT_COLOUR, WHITE );
+        txt_Set( TEXT_HIGHLIGHT, ODRD_BKGND );
+        txt_Set( FONT_ID, ODRD_FONT );
+        gfx_MoveTo( 5, gfx_Get( Y_MAX ) - 10 );
+        putnum( DEC, mem_Heap() );
+        gfx_MoveTo( 70, gfx_Get( Y_MAX ) - 10 );
+        putnum( DEC, comm_errors );
+
+        if ( touch_Get( TOUCH_STATUS ) == TOUCH_PRESSED )
+            x := touch_Get( TOUCH_GETX );
+            y := touch_Get( TOUCH_GETY );
+            if ( x >= 50 && x <= 100 && y >= 50 && y <= 70 )
+//                gfx_RectangleFilled( 120, 50, 140, 70, WHITE );
+//                while ( touch_Get( TOUCH_STATUS ) != TOUCH_RELEASED )
+//                    pause( 50 );
+//                wend
+//                gfx_RectangleFilled( 120, 50, 140, 70, RED );
+            else
+                var private btn;
+                btn := bcui_do_touch();
+                if ( btn == btn_Hello )
+                    file_PlayWAV( "CHIMES.WAV" );
+                else if ( btn == btn_There )
+                    file_PlayWAV( "magchime.wav" );
+                endif
+            endif
+        endif
+
+    forever
+
+endfunc
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/robots/odr-display-4d/odr-display-4d.cfg	Sat Jun 28 16:13:53 2014 -0700
@@ -0,0 +1,1 @@
+uLCD-32PTU,2,Picaso,uLCD-32PTU-A
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/robots/odr-display-bridge/jamfile	Sat Jun 28 16:13:53 2014 -0700
@@ -0,0 +1,48 @@
+# -----------------------------------------------------------------------------------------
+#
+#   Copyright (C) 2014 Bob Cook
+#
+#   Bob Cook Development, Robotics Library
+#   http://www.bobcookdev.com/rl/
+#    
+#   Permission is hereby granted, free of charge, to any person obtaining a copy
+#   of this software and associated documentation files (the "Software"), to deal
+#   in the Software without restriction, including without limitation the rights
+#   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#   copies of the Software, and to permit persons to whom the Software is
+#   furnished to do so, subject to the following conditions:
+#
+#   The above copyright notice and this permission notice shall be included in
+#   all copies or substantial portions of the Software.
+#
+#   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#   THE SOFTWARE.
+#
+# -----------------------------------------------------------------------------------------
+
+if $(TRACE) { echo "trace /robots/odr-display-bridge/jamfile" ; }
+
+SubDir TOP robots odr-display-bridge ;
+
+# -----------------------------------------------------------------------------------------
+
+avr_executable
+    odr-display-bridge atmega16m1
+    : main.cpp
+      canmsgs.cpp
+      comm.cpp
+      display.cpp
+      packages.avr.can.pkg
+      packages.avr.device.pkg
+      packages.avr.redir.pkg
+      packages.common.can.pkg
+      packages.common.util.pkg
+    ;
+
+# -----------------------------------------------------------------------------------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/robots/odr-display-bridge/main.cpp	Sat Jun 28 16:13:53 2014 -0700
@@ -0,0 +1,165 @@
+// ----------------------------------------------------------------------------------------
+//
+//  Copyright (C) 2014 Bob Cook
+//    
+//  Bob Cook Development, Robotics Library
+//  http://www.bobcookdev.com/rl/
+// 
+//  Bridge between the CAN messaging network and the 4D Systems touch LCD display.
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+// 
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+// 
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+//
+// ----------------------------------------------------------------------------------------
+
+#include <avr/interrupt.h>
+#include <avr/io.h>
+#include <avr/pgmspace.h>
+#include <avr/sleep.h>
+
+#include <ctype.h>
+#include <stdio.h>
+
+#include "project_defs.h"
+
+#include "func.h"
+
+#include "packages/avr/device/int_helpers.h"
+#include "packages/avr/device/spinwait.h"
+#include "packages/avr/device/uart.h"
+
+#include "packages/avr/redir/redir.h"
+
+#include "packages/common/util/misc.h"
+
+// always after other includes
+#include "packages/avr/device/workaround34734.h"
+
+// ----------------------------------------------------------------------------------------
+
+static inline void led_red_off() __attribute__((always_inline));
+static inline void led_red_off()
+{
+    PORTB &= ~( 1 << PB2 );
+}
+
+static inline void led_red_on() __attribute__((always_inline));
+static inline void led_red_on()
+{
+    PORTB |= ( 1 << PB2 );
+}
+
+static inline void led_red_toggle() __attribute__((always_inline));
+static inline void led_red_toggle()
+{
+    PORTB ^= ( 1 << PB2 );
+}
+
+static inline void led_green_off() __attribute__((always_inline));
+static inline void led_green_off()
+{
+    PORTC &= ~( 1 << PC4 );
+}
+
+static inline void led_green_on() __attribute__((always_inline));
+static inline void led_green_on()
+{
+    PORTC |= ( 1 << PC4 );
+}
+
+static inline void led_green_toggle() __attribute__((always_inline));
+static inline void led_green_toggle()
+{
+    PORTC ^= ( 1 << PC4 );
+}
+
+// ----------------------------------------------------------------------------------------
+
+void hw_init()
+{
+    //--    Turn off all interrupts.
+
+    cli();
+    interrupts_clear_all();
+
+    //--    Indicator LEDs on PORTB.2 and PORTC.4
+
+    DDRB |= ( 1 << PB2 );
+    led_red_off();
+
+    DDRC |= ( 1 << PC4 );
+    led_green_off();
+
+    //--    Blink the LED just to say hello.
+    
+    for ( uint8_t i = 0; i < 5; i++ )
+    {
+        led_red_on();
+        led_green_off();
+        spinwait_delay_ms( 75 );
+        led_red_off();
+        led_green_on();
+        spinwait_delay_ms( 75 );
+    }
+
+    led_red_off();
+    led_green_off();
+    
+    //--    Initialize the serial output.
+
+    comm_init();
+    //redir_initialize();
+
+    //--    Re-enable interrupts.
+
+    sei();
+}
+
+// ----------------------------------------------------------------------------------------
+
+int main()
+{
+    //--    Initialize the hardware.
+
+    hw_init();
+
+//    int i = 0;
+
+    for ( ;; )
+    {
+//        spinwait_delay_ms( 100 );
+//        printf_P( PSTR("Hello world!\n") );
+//        spinwait_delay_ms( 100 );
+//        led_green_toggle();
+//        spinwait_delay_ms( 100 );
+//        printf_P( PSTR("loop iteration %d\n"), i++ );
+//        led_green_toggle();
+
+        //--    Any pending CAN messages to receive/process?
+
+        if ( ! canmsg_process_pending() )
+        {
+            // ++can_comm_errors;
+        }
+    }
+
+    return 0;
+}
+
+// ----------------------------------------------------------------------------------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/robots/odr-display-bridge/project_defs.h	Sat Jun 28 16:13:53 2014 -0700
@@ -0,0 +1,57 @@
+// ----------------------------------------------------------------------------------------
+//
+//  Copyright (C) 2014 Bob Cook
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//  
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+//  
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+//
+// ----------------------------------------------------------------------------------------
+
+#if !defined( PROJECT_DEFS_H )
+#define PROJECT_DEFS_H
+
+// ----------------------------------------------------------------------------------------
+//  packages/avr/common
+
+#define PRJ_CPU_FREQ    16000000L
+
+// ----------------------------------------------------------------------------------------
+//  packages/avr/can
+
+#define PRJ_M1CAN_ENABLE
+#define PRJ_M1CAN_CANBUS_250_KHZ
+#define PRJ_M1CAN_TX_BUFFER_SIZE  8
+
+// ----------------------------------------------------------------------------------------
+//  packages/avr/device
+
+#define PRJ_UART0_USE_POLLED_MODE
+//#define PRJ_UART0_USE_INTERRUPT_MODE
+#define PRJ_UART0_EXTENDED_READ_WRITE
+
+// ----------------------------------------------------------------------------------------
+//  packages/avr/redir
+
+#define PRJ_REDIR_VIA_UART0
+#define PRJ_REDIR_UART_BAUDRATE         19200
+#define PRJ_REDIR_OUTPUT_DO_AUTO_CRLF
+
+
+#endif // #if !defined( PROJECT_DEFS_H )
+// ----------------------------------------------------------------------------------------
+
--- a/main/robots/odr-gps/project_defs.h	Sat Jun 28 16:12:28 2014 -0700
+++ b/main/robots/odr-gps/project_defs.h	Sat Jun 28 16:13:53 2014 -0700
@@ -36,6 +36,7 @@
 #define PRJ_M1CAN_ENABLE
 #define PRJ_M1CAN_CANBUS_250_KHZ
 #define PRJ_M1CAN_TX_BUFFER_SIZE  8
+#define PRJ_M1CAN_RX_BUFFER_SIZE  4
 
 // ----------------------------------------------------------------------------------------
 //  packages/avr/device