Program Description

Calculation of LMST : OverviewNotes

Sources

The program is written in PIC assembler, to be compiled by MPLAB X. The source files (.asm) and the include file (.inc) should be in the same directory, the linker file (.lnk) does not need to be. To create the MPLAB project, install MPLAB (it's free from the Microchip website). Create a new project (Microchip embedded, Standalone Project), select PIC16F628A as the device, no debugger, Simulator as the Hardware Tool, mpasm as the compiler, and choose a project name and directory. When the project opens, expand it (click the +). Right click Source Files and select Add Existing Item, and add all the .asm files. The same for Linker Files and the .lnk file. Select Run>Build Project and the program should compile and produce a file <project>dist/default/production/<name>.production.hex. This can then be programmed into the PIC using your chosen programmer.

Purists may desire a more modular program, but this was not done for several reasons: there is a  high level of interaction between the various sections; there is a limited stack; in the interests of speed the interrupt routines do not use subroutines.

Aims

  1. Receive and process $GPRMC NMEA messages from a GPS unit
  2. Use a GPS 1ppS input to synchronise time dependant operations
  3. Create a pulse each sidereal second
  4. Drive a time display in a timely manner

Considerations

  1. The reception of the 1ppS and NMEA messages is asynchronous to the creation of sidereal pulses and updating the display.
  2. The data in the $GPRMC message refers to a previous 1ppS event. The programming has to take this into account, and extrapolate forward in time.

Method

The heart of the program is TMR1, a 16 bit hardware counter updated at half the instruction time (nominally 500KHz). This is extended to 24 bit in software with counter TMR1X. Because the microprocessor is using an internal oscillator, the actual instruction rate is determined by calculating the difference in the counter (24 bit) value for two successive 1ppS events from the GPS unit. This should be an accurate measure of a second within a few microseconds, and the length of a sidereal second can be calculated by multiplying by a constant (approximately 0.99727).

The program functions as two relatively independent sections. One section deals with the input from the GPS - i.e. the 1ppS and serial input, the other with generating the sidereal output pulses and updating the display with sidereal time. The output is dependant on information provided by the input, this information is communicated by a two entity queue. Conceptually the input adds entities to the queue, and the output removes entities. The reality is slightly more complex because the output removes entities faster than they are added by the input. This is overcome by ensuring the queue is always filled with usable entities. An entity can be created by extrapolation, knowing that the value of the next sidereal second is one second more than the current one (wrapping around each 86400 seconds), and will occur a calculated number of TMR1 counts after the current one. This extrapolation can generate entities for a few seconds of output, if there is no input. The longer there is no input, the less reliable the result.  In practice, entities almost always are created as extrapolations then the TMR1 count is overwritten with calculated data from the input when it becomes available. The exception is that every 6 minutes approximately, there are two sidereal seconds within the time of one UTC second, only one will be calculated.

Each queue entity has two elements - a sidereal time as a number of seconds in a sidereal day (0-86399) and an extended count (TMR1 and TMR1X) when that second starts. There are also some flags used for housekeeping. The comparator is loaded with the TMR1 value of the current queue entity, which causes an interrupt each time TMR1 has that value. In the interrupt routine, the value of TMR1X is compared with the value in the queue. When this matches, it is the start of a new sidereal second. The program then removes the current queue entity, promotes the remaining entity to be current, and creates an extrapolated entity in its place. The comparator is loaded with the new values for the beginning of the next second, and the value of the now current second is sent to the display and the cycle is repeated. There is some additional processing which sets up the hardware to raise RB3 when the match occurs. Because the raising of RB3 is controlled by the hardware, it happens within 2uS of the desired time independant of the state of the software.

The input processing is broken into two discrete parts, processing the 1ppS input and processing the NMEA messages. The 1ppS events correlate the TMR1 timer with the actual time. Knowing the value of the TMR1 counter (extended to 24 bit) for two successive 1ppS events allows calculation of the value of the counter for any time in the next 30 seconds (after which the counter rolls around). The program calculates less than 3 seconds ahead, so this is more than adequate. Note that the 1ppS event alone does not determine the time. That information is given in a following NMEA message.

The desired $GPRMC message is part of a character stream sent at 9600 baud by the GPS unit. Each character causes an interrupt, the interrupt routine is responsible for sifting the input for the required message, ensuring the message is valid, and extracting the desired data (the date, time and longitude). Although the interrupt occurs about every 1mS, the processing overhead is quite small and has little effect on processing cycles available for other tasks. Once a valid set of data has been extracted, processing the data is handed to a background task. Because the background task is initiated after the 1ppS event to which the data refers, calculating relative to that event can result in sidereal data for a time which has already passed. Instead, the task calculates the time of the next whole sidereal second after the the next expected 1ppS event. This is why a two entity queue is used. The calculation may result in either the next sidereal second or the one after, and the task overwrites whichever is appropriate. This makes the background task timing very flexible, and it can be delayed by other tasks without consequence.

It is desirable to update the display in a timely manner. If the update were scheduled as a background task, it could be delayed because another background task (e.g. the calculations using the message data) is in progress. However, it is too long (about 1mS) to be part of an interrupt routine. Other interrupts (e.g. the message interrupts) could be inhibited long enough that data was lost or unacceptably delayed. The solution is an interruptable thread. When an interrupt detects that a display update is needed, instead of scheduling a background task and returning in the normal way, it branches directly to the display routine. The display routine takes a copy of the background status when the interrupt was initiated, then reenables interrupts. This allows interrupt processing in the normal way, but the display routine runs to completion without being delayed by background tasks. It then restores the background state from its copy and does a normal return, allowing the background task to continue from where it was interrupted.

Program structure

During program startup, four interrupts are enabled: TMR1 rollover, TMR1/CCPR1 match, RB0 transition (for GPS 1ppS) and USART receive (for GPS serial data). The background process then goes into an endless loop testing the byte Alert for a value other than zero. Each of the four interrupt routines will increment Alert when certain conditions are met, at which time the background process will decrement Alert and activate a corresponding background routine. When the background routine completes it returns to the endless loop.

The TMR1 rollover interrupt is enabled so TMR1X can be incremented for each rollover. It has a background routine only so that the 1ppS interrupt can determine if it was delayed by another interrupt (see below).

The TMR1/CCPR1 match interrupt tests for a match between the extended counter TMR1X and the corresponding value in the queue. A flag is set when there is a TMR1X/queue match so the background routine will prepare for the next sidereal second. The background routine is activated for every TMR1/CCPR1 match so that the 1ppS interrupt can determine if it was delayed by another interrupt (see below). If there is a TMR1X/queue match, and the selected display is LMST, the interrupt routine completes by branching to the display routine.  The interrupt routine also sets up the hardware registers so RB3 is raised and lowered at the correct times. RB3 is only set to go high when TMR1X is found to be 1 less than the queue value. That causes RB3 to be set high by the hardware on the next TMR1/CCPR1 match at which time TMR1X/queue should also match. At all other times the match sets RB3 low. The first match after completion of a sidereal second occurs after about 80mS, with some variation due to the deviation of the internal oscillator from its nominal 4MHz.

The background routine for the TMR1/CCPR1 match completes immediately unless the flag indicates a TMR1X/queue match as well. On a full match, the routine shuffles the queue, loads a new value in CCPR1, and converts the time value in the queue (in binary seconds) to BCD suitable for the next display of LMST time.

The RB0 transition interrupt is initiated by a 1ppS event, and captures the value of TMR1/TMR1X each time it is activated. If there are two successive 1ppS events the background routine is activated to calculate the number of TMR1 counts in a second. For this to be accurate it is important that the time from the 1ppS event to the capture of TMR1/TMR1X be constant. If any interrupt routine was in progress when the 1ppS event occurs, the RB0 transition interrupt will be activated after the previous routine completes and therefore the capture is late. To determine if this happened, the routine tests if flags were set by the TMR1 rollover or TMR1/CCPR1 match interrupts but not yet cleared by the corresponding background routines. If the flags are not set there was no delay otherwise the 1ppS event is ignored. An assumption is there are no USART interrupts so this is not checked, the serial data stream should start after a 1ppS event and complete before the next one. Note that under unusual circumstances a flag may be set but there was no delay. The program is designed to tolerate occasional missed 1ppS events, so this is inconsequential. The user can elect to display UTC instead of LMST, and if that is the case the interrupt routine completes by branching to the display routine.

Most of the program code deals with the serial data stream and the data extracted from it. The interrupt routine is quite complex and coded for minimum overhead as it is activated hundreds of times a second. As a result the interrupt routine takes as little as 25uS but always less than 100uS to process a character, using less than 10% of the available CPU cycles. The corresponding background routine is only activated when a $GPRMC message has been validated, and the required data has been extracted. The background routine consists of numerous steps but is logically simple. The LMST time at the next 1ppS event is calculated, then the time of the next sidereal second after that. This is converted to a TMR1/1X value and the queue is updated.