/** * @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(); }