This is post #6 in a series covering the hardware and software design for my work-in-progress GPS-guided rocket recovery project. The main index to the series of posts is here, and an introduction to the project (a PowerPoint presentation) is here.
This is the last post about the source code, it covers the interrupt service routine (ISR) in isr.c.
On any interrupt, PIC instruction execution goes to memory location 0x0008, where int_vector() causes execution of the interrupt service routine (ISR), isr().
Z-80 SPEEDOMETER
On entry to the ISR, macro PUSH_DEBUG_STATE() (in debug.h) stores the current state of the Z-80 speedometer output SPEEDO in variable pushed_debug_state. This will be restored on exit from the ISR by macro POP_DEBUG_STATE. (BTW, the only thing this has to do with a Z-80 is that’s what I’ve been calling this trick since I first did it on a Z-80 project back in the early ’80s…)
Then the ISR raises the SPEEDO output, because regardless of whether the PIC was busy when the interrupt occurred, it is busy during interrupt servicing.
TIMER1 (RTC) RESET
The ISR then checks PIR1bits.TMR1IF to see if the interrupt was caused by Timer1 overflowing, which generates the RTC tick interrupt. The result is recorded in variable timer1_expired.
If Timer1 did overflow, it is reset by reloading it with the value TMR_START, to trigger the next RTC interrupt at the proper time.
Note that the value of TMR_START (calculated in hardware.h) accounts for the 6 Timer1 tick periods of interrupt latency between Timer1 overflowing and the ISR resetting it. A few NOP instructions are executed before resetting Timer1 to round up this time to a whole number of Timer1 ticks. (The number of NOPs needed was found using Microchip’s PIC18 simulator, included in the MPLAB IDE).
Ideally, the first peripheral to be serviced in the ISR would be the UART receive buffer. At 38,400 bps data bytes from the GPS can arrive as little as 260 microseconds apart, making this interrupt much more time-critical than others. But servicing the UART involves enough conditionals that predicting the exact number of CPU cycles becomes complicated, making it difficult to keep the RTC tick interval accurate.
So instead, the Timer1 interrupt is dealt with first, but just the minimum necessary to reset it. Other tasks that will be performed on the RTC interrupt are postponed until after dealing with GPS data in the UART receive buffer.
RECEIVED DATA
While there is data to be read in the UART receive buffer (PIR1bits.RCIF == TRUE), the data is read out of the UART and stored in NMEAbuffer[] for later parsing by CheckForNewGPSFix() in main().
Before each byte is stored in the buffer, I check:
if (RTC - LastUARTrxRTC > GPS_BURST_PERIOD_MS/RTC_INTERRUPT_INTERVAL_MS)
If so, this means that no data has been received from the GPS for longer than one GPS data burst period. Since the time between data bursts (typically 950 mS or so) is longer than the duration of each burst (typically 50 mS), this indicates that this is the first byte of a new burst, and so the current RTC value is saved in global GPSBurstStartRTC. This is logged with the GPS fix data, to indicate the exact time the fix burst started in terms of the RTC.
Next, the current RTC time is stored in LastUARTrxRTC. This will be used byFlushFlashBufferSafe() and BetweenGPSFixesFor() (both in hardware.c) to detrmine if we are current receiving a GPS data burst, and so if writing to Flash memory needs to be postponed.
If the received byte was a comma, it is converted into a zero. This transforms the NEMA-0183 formatted GPS message (a series of comma separated values) into a series of zero-terminated C strings, to make parsing of the data simpler for CheckForNewGPSFix().
If the overrun flag (RCSTAbits.OERR) was set, this indicates that the UART receive data buffer overflowed (was overrun) and one or more bytes of data were lost. This should never happen, and I don’t think it ever does. But just in case I’m wrong (for example if the CPU clock speed were reduced, the UART baud rate increased, or the ISR changed, this could happen), the flag is reset and the entire line of received data in the buffer is discarded – it’s important to avoid parsing corrupted data. (A wrong GPS fix is much more confusing to the navigation algorithm than a missed one.)
RTC SERVICING
Once the UART has been dealt with, the timer1_expired flag, which was set earlier in the ISR, is checked. If the flag is set, this indicates that the ISR needs to service the Timer1 RTC interrupt, regardless of whether or not there was any received UART data to process.
Macro ServiceBeep() (defined in peripherals.h) is called twice in the RTC handling portion of the ISR – here and again at the end. On each call, this inverts the PIEZO output bit (to the piezo buzzer) if the global variable Beep is set. Since the RTC timer runs at 1 kHz (1 mS intervals), this generates a 1 kHz tone whenever Beep is set – this is used, for example, by BeepOutMaxAltitude() which is called in the FULL state.
Continue reading