/**
* @mainpage LED Clock
*
* @section overview_sec Overview
*
* Master controller for large, 4" tall 7-segment LED clock. The clock
* contains 3 PIC18F252 controllers. One for each set of hours, minutes,
* and seconds digits. The seconds display is the master controller and
* controls a Trimble Resolution T GPS engine for timing.
*
* @section history_sec Revision History
*
* @subsection v100 V1.00
* 5 Mar 2006, M.Gray, Initial release
*/
// Hardware specific configuration.
#include <18f252.h>
// External crystal oscillator.
#fuses HS
// Disable watch dog timer.
#fuses NOWDT
// Protect the code so it can't be read back.
#fuses NOPROTECT
// Disable the power-up enable timer.
#fuses NOPUT
// Disable the brown out detect.
#fuses NOBROWNOUT
// Disable the low voltage programming mode.
#fuses NOLVP
// We define types that are used for all variables. These are declared
// because each processor has a different sizes for int and long.
// The PIC compiler defines boolean, int8, int16, and int32.
/// Boolean value { false, true }
typedef boolean bool_t;
/// Signed 8-bit number in the range -128 through 127.
typedef signed int8 int8_t;
/// Unsigned 8-bit number in the range 0 through 255.
typedef unsigned int8 uint8_t;
/// Signed 16-bit number in the range -32768 through 32767.
typedef signed int16 int16_t;
/// Unsigned 16-bit number in the range 0 through 65535.
typedef unsigned int16 uint16_t;
/// Signed 32-bit number in the range -2147483648 through 2147483647.
typedef signed int32 int32_t;
/// Unsigned 32-bit number in the range 0 through 4294967296.
typedef unsigned int32 uint32_t;
/// IEEE 32-bit floating point number.
typedef struct
{
/// Array that holds 32-bit IEEE floating pointer number.
unsigned int8 value[4];
} float_t;
/// IEEE 64-bit floating point number.
typedef struct
{
/// Array that holds 64-bit IEEE floating point number.
unsigned int8 value[8];
} double_t;
void gpsInit();
bool_t gpsIsReady();
void gpsUpdate();
bool_t serialHasData();
void serialInit();
uint8_t serialRead();
void serialUpdate();
inline uint16_t sysUint16GPSToHost (uint16_t value);
inline uint32_t sysUint32GPSToHost (uint32_t value);
inline float sysFloatGPSToHost (uint32_t value);
void sysInit();
// These compiler directives set the clock, SPI/I2C ports, and I/O configuration.
// Frequency of crystal
#use delay(clock=3686400)
// GPS engine
#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7)
#use i2c (master, scl=PIN_C3, sda=PIN_C4)
// We'll set the I/O direction so we can just read/write I/O pins.
#use fast_io(A)
#use fast_io(B)
#use fast_io(C)
/**
* @defgroup GPS GPS Engine
*
* Functions to control the Trimble Resolution T GPS engine in TSIP binary mode.
* See the January 2005 (Revision A) of the Resolution T System Designer Reference
* Manual part number 54655-05 for details on the Resolution T GPS engine.
*
* On start-up, the GPS engines send two binary messages after each 1-PPS event.
* The messages are the primary and supplemental timing packets.
*
* @{
*/
/// The maximum length of a binary GPS engine message.
#define GPS_BUFFER_SIZE 80
/// No timing packets recevied.
#define GPS_UPDATE_NO_PACKET 0x00
/// Primary packet (0x8f - 0xab) processed.
#define GPS_UPDATE_PRIMARY_PACKET 0x01
/// Supplemental packet (0x8f - 0xac) processed.
#define GPS_UPDATE_SUPP_PACKET 0x02
/// All packets received.
#define GPS_UPDATE_ALL_PACKETS 0x03
/// GPS time information from primary and supplemental packets.
typedef struct
{
/// Month in GPS time.
uint8_t month;
/// Day of month in GPS time.
uint8_t day;
/// Year in GPS time.
uint16_t year;
/// Hours in GPS time.
uint8_t hours;
/// Minutes in GPS time.
uint8_t minutes;
/// Seconds in GPS time.
uint8_t seconds;
/// UTC offset in seconds.
uint16_t utcOffset;
/// GPS decoding status. Reference manual for table.
uint8_t decodingStatus;
} GPS_TIME_STRUCT;
/// GPS parse engine state machine values.
enum GPS_PARSE_STATE_MACHINE
{
/// Start of message delimiter.
GPS_DLE,
/// Message data bytes.
GPS_DATA,
/// Extra DLE within message.
GPS_EXTRA_DLE
};
/// Resolution-T message packet format for Primary Timing Packet (0x8f-ab).
typedef struct
{
/// TSIP Super-packet identifier, always 0x8f for this message.
uint8_t messageID;
/// Always 0xab for this message.
uint8_t subcode;
/// GPS seconds of week.
uint32_t timeOfWeek;
/// GPS Week Number.
uint16_t weekNumber;
/// UTC Offset (seconds).
uint16_t utcOffset;
/// Timing flag bit field. Reference manual for bit usage.
uint8_t timingFlag;
/// Time in seconds.
uint8_t seconds;
/// Time in minutes.
uint8_t minutes;
/// Time in hours.
uint8_t hours;
/// Day of month.
uint8_t day;
/// Month of year.
uint8_t month;
/// Four digits of year.
uint16_t year;
} GPS_PRIMARY_PACKET;
/// Resolution-T message packet format for Supplemental Timing Packet (0x8f-ac).
typedef struct
{
/// TSIP Super-packet identifier, always 0x8f for this message.
uint8_t messageID;
/// Always 0xac for this message.
uint8_t subcode;
/// Reeiver mode. Reference manual for table.
uint8_t receiverMode;
/// Reserved.
uint8_t reserved1;
/// Self survey progress in the range 0 to 100%.
uint8_t selfSurveyProgress;
/// Reserved.
uint32_t reserved2;
/// Reserved.
uint16_t reserved3;
/// Minor alarms bit field. Reference manual for bit usage.
uint16_t minorAlarms;
/// GPS decoding status. Reference manual for table.
uint8_t decodingStatus;
/// Reserved.
uint8_t reserved4;
/// Spare status 1.
uint8_t spareStatus1;
/// Spare status 2.
uint8_t spareStatus2;
/// Local clock bias.
float_t localClockBias;
/// Local clock bias rate.
float_t localClockBiasRate;
/// Reserved.
uint32_t reserved5;
/// Reserved.
float_t reserved6;
/// Temperature in degrees C.
float_t temperature;
/// Latitude in radians.
double_t latitude;
/// Longitude in radians.
double_t longitude;
/// Altitude in meters.
double_t altitude;
/// PPS Quantization error in seconds.
float_t ppsQuantizationError;
/// Spare / future expansion.
uint8_t spare[4];
} GPS_SUPPLEMENTAL_PACKET;
/// Index into gpsBuffer used to store message data.
uint8_t gpsIndex;
/// State machine used to parse the GPS message stream.
GPS_PARSE_STATE_MACHINE gpsParseState;
/// Buffer to store data as it is read from the GPS engine.
uint8_t gpsBuffer[GPS_BUFFER_SIZE];
/// Last complete timing report.
GPS_TIME_STRUCT gpsTime;
/// Bitmap used to track when each of the timing reports is received.
uint8_t gpsUpdateBitmap;
/**
* Initialize the GPS subsystem.
*/
void gpsInit()
{
// Initial parse state.
gpsParseState = GPS_DLE;
// Initial bitmap state.
gpsUpdateBitmap = GPS_UPDATE_NO_PACKET;
// Clear the structure that stores the position message.
memset (&gpsTime, 0x00, sizeof(gpsTime));
}
/**
* Determine if new GPS message is ready to process. This function is a one shot and
* typically returns true once a second for each GPS position fix.
*
* @return true if new message available; otherwise false
*/
bool_t gpsIsReady()
{
if (gpsUpdateBitmap == GPS_UPDATE_ALL_PACKETS)
{
gpsUpdateBitmap = GPS_UPDATE_NO_PACKET;
return true;
} // END if
return false;
}
/**
* Process the primary timing packet.
*
* @param packet pointer to GPS_PRIMARY_PACKET structure
*/
void gpsProcessPrimaryPacket (GPS_PRIMARY_PACKET *packet)
{
gpsTime.month = packet->month;
gpsTime.day = packet->day;
gpsTime.year = sysUint16GPSToHost(packet->year);
gpsTime.hours = packet->hours;
gpsTime.minutes = packet->minutes;
gpsTime.seconds = packet->seconds;
gpsTime.utcOffset = sysUInt16GPSToHost(packet->utcOffset);
// Update the bitmap that indicates we processed this message.
gpsUpdateBitmap |= GPS_UPDATE_PRIMARY_PACKET;
}
/**
* Process the supplemental timing packet.
*
* @param packet pointer to GPS_SUPPLEMENTAL_PACKET structure
*/
void gpsProcessSupplementalPacket (GPS_SUPPLEMENTAL_PACKET *packet)
{
gpsTime.decodingStatus = packet->decodingStatus;
// Update the bitmap that indicates we processed this message.
gpsUpdateBitmap |= GPS_UPDATE_SUPP_PACKET;
}
/**
* Determine the type of GPS message and parse it.
*/
void gpsParseMessage()
{
// Byte 0 is the messageID.
switch (gpsBuffer[0])
{
case 0x45:
return;
case 0x8f:
// Byte 1 identifies the super packet type.
switch (gpsBuffer[1])
{
case 0xab:
gpsProcessPrimaryPacket (gpsBuffer);
return;
case 0xac:
gpsProcessSupplementalPacket (gpsBuffer);
return;
} // END switch
} // END swich
}
/**
* Read the serial FIFO and process complete GPS messages.
*/
void gpsUpdate()
{
uint8_t value;
// This state machine handles each characters as it is read from the GPS serial port.
// This parses the Resolution T TSIP binary message protocol.
while (serialHasData())
{
// Get the character value.
value = serialRead();
// Process based on the state machine.
switch (gpsParseState)
{
case GPS_DLE:
// Wait for the message delimiter character.
if (value == 0x10)
{
gpsParseState = GPS_DATA;
gpsIndex = 0;
} // END if
break;
case GPS_DATA:
// Save each value to the data buffer.
gpsBuffer[gpsIndex] = value;
// If we filled the buffer without detecting the sequence start over.
if (++gpsIndex == GPS_BUFFER_SIZE)
gpsParseState = GPS_DLE;
// If the DLE character is detected, it either means the end of message or a byte stuffed value.
if (value == 0x10)
gpsParseState = GPS_EXTRA_DLE;
break;
case GPS_EXTRA_DLE:
switch (value)
{
// End of message character.
case 0x03:
gpsParseMessage();
gpsParseState = GPS_DLE;
break;
case 0x10:
gpsParseState = GPS_DATA;
break;
default:
gpsParseState = GPS_DLE;
} // END switch
break;
} // END switch
} // END while
}
/** @} */
/**
* @defgroup led LED Driver
*
* Drives the multiplex LED display.
*
* @{
*/
// Map I/O pin names to hardware pins.
/// LED Digit 1 - Port B3
#define IO_LED_DIGIT_1 PIN_B3
/// LED Digit 2 - Port C5
#define IO_LED_DIGIT_2 PIN_C5
/// LED segment 'B' - Port C0
#define IO_LED_SEGMENT_B PIN_C0
/// LED segment 'F' - Port B4
#define IO_LED_SEGMENT_F PIN_B4
/// LED segment 'G' - Port B5
#define IO_LED_SEGMENT_G PIN_B5
/// Bitmask to control intra-display colon.
#define LED_COLON 0x01
/// Bitmask for LED segment 'C'.
#define LED_SEG_C 0x02
/// Bitmask for LED segment 'D'.
#define LED_SEG_D 0x04
/// Bitmask for LED segment 'E'.
#define LED_SEG_E 0x08
/// Bitmask for LED segment 'B'.
#define LED_SEG_B 0x10
/// Bitmask for LED segment 'A'.
#define LED_SEG_A 0x20
/// Bitmask for LED segment 'F'.
#define LED_SEG_F 0x40
/// Bitmask for LED segment 'G'.
#define LED_SEG_G 0x80
/// Value index that drives all segments off.
#define LED_ALL_SEGMENTS_OFF 0x10
/// Lookup table that translates number in the range 0 to 16 to equivalent 7-segment display.
const uint8_t LED_SEG_ARRAY[] =
{
// Digit 0
LED_SEG_A | LED_SEG_B | LED_SEG_C | LED_SEG_D | LED_SEG_E | LED_SEG_F,
// Digit 1
LED_SEG_B | LED_SEG_C,
// Digit 2
LED_SEG_A | LED_SEG_B | LED_SEG_D | LED_SEG_E | LED_SEG_G,
// Digit 3
LED_SEG_A | LED_SEG_B | LED_SEG_C | LED_SEG_D | LED_SEG_G,
// Digit 4
LED_SEG_B | LED_SEG_C | LED_SEG_F | LED_SEG_G,
// Digit 5
LED_SEG_A | LED_SEG_C | LED_SEG_D | LED_SEG_F | LED_SEG_G,
// Digit 6
LED_SEG_A | LED_SEG_C | LED_SEG_D | LED_SEG_E | LED_SEG_F | LED_SEG_G,
// Digit 7
LED_SEG_A | LED_SEG_B | LED_SEG_C | LED_SEG_F,
// Digit 8
LED_SEG_A | LED_SEG_B | LED_SEG_C | LED_SEG_D | LED_SEG_E | LED_SEG_F | LED_SEG_G,
// Digit 9
LED_SEG_A | LED_SEG_B | LED_SEG_C | LED_SEG_D | LED_SEG_F | LED_SEG_G,
// Digit A
LED_SEG_A | LED_SEG_B | LED_SEG_C | LED_SEG_E | LED_SEG_F | LED_SEG_G,
// Digit B
LED_SEG_C | LED_SEG_D | LED_SEG_E | LED_SEG_F | LED_SEG_G,
// Digit C
LED_SEG_A | LED_SEG_D | LED_SEG_E | LED_SEG_F,
// Digit D
LED_SEG_B | LED_SEG_C | LED_SEG_D | LED_SEG_E | LED_SEG_G,
// Digit E
LED_SEG_A | LED_SEG_D | LED_SEG_E | LED_SEG_F | LED_SEG_G,
// Digit F
LED_SEG_A | LED_SEG_E | LED_SEG_F | LED_SEG_G,
// 0x10 - All segments off
0x00
};
/// Segments to drive on left-hand LED.
uint8_t ledSegment1;
/// Segments to drive on right-hand LED.
uint8_t ledSegment2;
/// 5mS clock rate
#define LED_PWM_RATE 576
/// Timer 1 compare interrupt value.
uint16_t ledTimerCompare;
/// Flag used to determine if left-hand LED is updated during this interrupt period.
bool_t ledDigit1Flag;
/**
* Initialize the LED driver.
*/
void ledInit()
{
ledSegment1 = 0x00;
ledSegment2 = 0x00;
ledDigit1Flag = false;
ledTimerCompare = LED_PWM_RATE;
CCP_1 = LED_PWM_RATE;
set_timer1(ledTimerCompare);
setup_ccp1( CCP_COMPARE_INT );
setup_timer_1 (T1_INTERNAL | T1_DIV_BY_8);
}
/**
* Convert a number in the range 0 to 16 to its equivalent 7-segment LED value. The
* values 0 to 15 are displayed as hexadecimal digits and 16 is all segments off.
*
* @param digit in the range 0 to 16
*
* @return Segments of 7-segment LED to illuminate
*/
uint8_t ledGetSegments (uint8_t digit)
{
if (digit > sizeof(LED_SEG_ARRAY))
return 0x00;
return LED_SEG_ARRAY[digit];
}
/**
* Set the segments that will be driven in the multiplex timer routine.
*
* @param segment1 left-hand LED segments to set
* @param segment2 right-hand LED segmetns to set
*/
void ledSetSegments (uint8_t segment1, uint8_t segment2)
{
ledSegment1 = segment1;
ledSegment2 = segment2;
}
/**
* Set the a remote display's segments that will be driven in the multiplex timer routine.
*
* @param address remote device I2C address
* @param segment1 left-hand LED segments to set
* @param segment2 right-hand LED segmetns to set
*/
void ledSetRemoteSegments (uint8_t address, uint8_t segment1, uint8_t segment2)
{
i2c_start();
i2c_write(address);
delay_us(500);
i2c_write (segment1);
delay_us(500);
i2c_write (segment2);
delay_us(500);
i2c_stop();
}
/**
* Drive the hardware output ports with the segment mask.
*
* @param segments segment mask
*/
void ledWriteSegments (uint8_t segments)
{
output_a (segments & 0x2f);
output_bit (IO_LED_SEGMENT_B, (segments & LED_SEG_B));
output_bit (IO_LED_SEGMENT_F, (segments & LED_SEG_F));
output_bit (IO_LED_SEGMENT_G, (segments & LED_SEG_G));
}
/**
* Interrupt Service Routine that process tick that multiplexes LED display.
*/
#INT_CCP1
void ledTimer()
{
// Setup the next interrupt for the operational mode.
ledTimerCompare += LED_PWM_RATE;
CCP_1 = ledTimerCompare;
// Toggle between left and right-hand digits.
if (ledDigit1Flag)
{
output_low (IO_LED_DIGIT_1);
ledDigit1Flag = false;
ledWriteSegments (ledSegment1);
output_high (IO_LED_DIGIT_2);
} else {
output_low (IO_LED_DIGIT_2);
ledDigit1Flag = true;
ledWriteSegments (ledSegment2);
output_high (IO_LED_DIGIT_1);
} // END if-else
}
/** @} */
/**
* @defgroup serial Serial Port FIFO
*
* FIFO for the built-in serial port.
*
* @{
*/
/// Size of serial input port FIFO in bytes. Must be a power of 2, i.e. 2, 4, 8, 16, etc.
#define SERIAL_BUFFER_SIZE 128
/// Mask to wrap around at end of circular buffer. (SERIAL_IN_BUFFER_SIZE - 1)
#define SERIAL_BUFFER_MASK 0x7f
/// Index to the next free location in the buffer.
uint8_t serialHead;
/// Index to the next oldest data in the buffer.
uint8_t serialTail;
/// Circular buffer (FIFO) to hold serial data.
uint8_t serialBuffer[SERIAL_BUFFER_SIZE];
/**
* Determine if the FIFO contains data.
*
* @return true if data present; otherwise false
*/
bool_t serialHasData()
{
if (serialHead == serialTail)
return false;
return true;
}
/**
* Initialize the serial FIFO.
*/
void serialInit()
{
serialHead = 0;
serialTail = 0;
}
/**
* Get the oldest character from the FIFO.
*
* @return oldest character; 0 if FIFO is empty
*/
uint8_t serialRead()
{
uint8_t value;
// Make sure we have something to return.
if (serialHead == serialTail)
return 0;
// Save the value.
value = serialBuffer[serialTail];
// Update the pointer.
serialTail = (serialTail + 1) & SERIAL_BUFFER_MASK;
return value;
}
/**
* Read and store any characters in the PIC serial port in a FIFO.
*/
#INT_RDA
void serialISR()
{
// Save the value in the FIFO.
serialBuffer[serialHead] = fgetc();
// Move the pointer to the next open space.
serialHead = (serialHead + 1) & SERIAL_BUFFER_MASK;
}
/** @} */
/**
* @defgroup sys System Library Functions
*
* Generic system functions similiar to the C run-time library.
*
* @{
*/
/**
* Convert a 16-bit value's endian order.
*
* @param value to convert
*
* @return reserve endian value
*/
inline uint16_t sysUint16GPSToHost (uint16_t value)
{
return ((value >> 8) & 0x00ff) | ((value << 8) & 0xff00);
}
/**
* Convert a 32-bit value's endian order.
*
* @param value to convert
*
* @return reserve endian value
*/
inline uint32_t sysUint32GPSToHost (uint32_t value)
{
return ((value >> 24) & 0x0000ff) | ((value >> 8) & 0x0000ff00) | ((value << 8) & 0x00ff0000) | ((value << 24) & 0xff000000);
}
/**
* Initialize the system library and global resources.
*/
void sysInit()
{
// Set all outputs low.
output_a (0x00);
output_b (0x00);
output_c (0x00);
// Configure the port direction (input/output).
set_tris_a (0x10);
set_tris_b (0x47);
set_tris_c (0x1c);
// Disable all ADC ports.
setup_adc_ports (NO_ANALOGS);
port_b_pullups (true);
}
/** @} */
/**
* @defgroup app Device application
*
* Provides the main functionality for the device.
*
* @{
*/
#define APP_MINUTE_DISPLAY_ADDRESS 0x42
#define APP_HOUR_DISPLAY_ADDRESS 0x40
/**
* Initialize the application specific variables and required processor resources.
*/
void appInit()
{
// Setup the interrupts.
enable_interrupts(INT_RDA);
enable_interrupts(INT_CCP1);
enable_interrupts(GLOBAL);
delay_ms(100);
// Turn on all the segments to indicate we've booted.
ledSetSegments (ledGetSegments(0x08), ledGetSegments(0x08));
ledSetRemoteSegments (APP_MINUTE_DISPLAY_ADDRESS, ledGetSegments(0x08), ledGetSegments(0x08));
ledSetRemoteSegments (APP_HOUR_DISPLAY_ADDRESS, ledGetSegments(0x08), ledGetSegments(0x08));
}
/**
* User application.
*/
void appRun()
{
uint8_t hours, minutes, seconds, segment1, segment2;
int32_t timeOfDay;
// This is the main loop that process GPS data and waits for the once per second timer tick.
for (;;)
{
// Read the GPS engine serial port FIFO and process the GPS data.
gpsUpdate();
// Process the GPS data set when we get a complete set.
if (gpsIsReady())
{
// Convert the time of day to units in seconds.
timeOfDay = (uint32_t) (gpsTime.hours) * 3600l + (uint32_t) (gpsTime.minutes) * 60l + (uint32_t) gpsTime.seconds;
// Adjust for UTC / GPS offset.
timeOfDay -= (uint32_t) gpsTime.utcOffset;
// Adjust to my local time zone (-7 UTC) where 25200 = 7 * 60 min/hours * 60 sec/min
timeOfDay -= 25200;
// Wrap around our 24-hour clock.
if (timeOfDay < 0)
timeOfDay += 86400;
// Convert time of day in seconds to hours:minutes:seconds.
seconds = timeOfDay % 60;
minutes = (timeOfDay / 60) % 60;
hours = (timeOfday / 3600) % 12;
// Set the seconds display.
segment1 = ledGetSegments(seconds / 10);
segment2 = ledGetSegments(seconds % 10);
ledSetSegments (segment1, segment2);
// Set the minutes display.
segment1 = ledGetSegments(minutes / 10);
segment2 = ledGetSegments(minutes % 10);
// When the decode status is 0x00, it indicates the GPS is tracking, otherwise we'll flash the colon.
if (gpsTime.decodingStatus == 0x00)
segment1 |= LED_COLON;
else
if (gpsTime.seconds & 0x01)
segment1 |= LED_COLON;
ledSetRemoteSegments (APP_MINUTE_DISPLAY_ADDRESS, segment1, segment2);
// Set the hours display.
// Supress any leading 0 hours values, i.e. 1 o'clock, 2 o'clock, etc.
switch (hours)
{
case 0:
segment1 = ledGetSegments(1);
segment2 = ledGetSegments(2);
break;
case 10:
case 11:
segment1 = ledGetSegments(1);
segment2 = ledGetSegments(hours % 10);
break;
default:
segment1 = ledGetSegments(LED_ALL_SEGMENTS_OFF);
segment2 = ledGetSegments(hours % 10);
break;
} // END switch
segment1 |= LED_COLON;
ledSetRemoteSegments (APP_HOUR_DISPLAY_ADDRESS, segment1, segment2);
} // END if
} // END for
}
/** @} */
/// Function where everything gets started.
void main()
{
// Configure the basic system.
sysInit();
// Wait for the power converter chains to stabilize.
delay_ms (50);
// Setup the subsystems.
gpsInit();
serialInit();
ledInit();
appInit();
// Run the end application.
appRun();
}