This section provides a "narrative" description of the PIC program, mostly for the benefit of microcomputer "novices" ( or non-novices, unfamiliar with PIC chip architecture ) who might otherwise find it difficult to "decode" the SOURCE code listing, provided in the next section.

This program uses "input polling" and one interrupt ( for the CALIB 1-shot ) to perform the necessary tasks. Program execution begins at address 0000 when power is applied. An interrupt automatically causes the CPU to call an interrupt subroutine located at address 0004, and the return address is ( automatically ) pushed onto the "stack".

When an interrupt occurs, further interrupts are automatically disabled, and are re-enabled only when the GIE bit in the INTCON control register is set to logic 1. This can be done under software control, ( as it is here ) or it can be done "automatically" when the interrupt routine finishes its task, by executing a "RETFIE" instruction. ( RETurn From IntErrupt )

GENERAL INFO : This chip can accept interrupts from 4 different sources, but only one is employed in this design. In designs employing more than one interrupt, the CPU must examine the INTCON register to determine which particular source has generated an interrupt, so it can "figure out" what to do next. The INTCON register is NOT examined in this software because only one interrupt source is used.

Most of the software in this design is actually part of the interrupt subroutine, which is triggered by the rising edge of the pulse from U9 pin 6, ( = trailing edge of the calibration 1 - shot ) which is applied to pin 6 of the PIC chip. The "D" bit of the original divide-by-16 counter ( U6 ) is applied to pin 1 of the PIC chip, and the falling edge of this signal is used to reset the internal 0-359 bearing counter. An external X720 clock for the bearing counter is applied to PIC pin 3, and divided ( internally ) by two before being applied to the bearing counter. PIC Pin 2 provides the RS232 output signal, and the baud rate clock for this signal is generated internally by software timing loops, based on a 3.58 MHz crystal frequency.

When power is applied to the chip, execution begins at address 0000. The program immediately jumps to address ( hex ) 0010, to "bypass" the interrupt handler routine, which must ( by chip design ) reside at address 0004. The "startup" routine configures and "defines" each of the various I/O pins and signals, and several internal functions ( timer0 and interrupts ) that are later used by the software.

Timer0, and its "overflow" bit, are used together as the 0-359 bearing counter. Once the timer0 counter is configured, no further software intervention is required to increment it... the clock that drives this counter is provided externally, at pin 3, and clocking is performed entirely by hardware within the PIC chip. This counter will not automatically "rollover" ( to zero ) when it reaches 360... The "D" bit of the divide-by-16 counter ( located on the main board ) is used to reset the counter to zero... the falling edge of this signal occurs ONLY when the divide-by-16 counter "rolls over" to zero, and (obviously ) both counters should reach "zero" simultaneously. This is achieved in software.

The internal prescaler is used to divide the X720 clock down to X360. This prescaler can be configured in software for a variety of different division ratios. When the calibration 1-shot generates a ( rising edge ) trigger pulse at pin 6, the number in the timer0 counter ( at that exact instant ) represents the signal bearing, expressed in binary form.

Unfortunately, there is no simple way ( with software ) to inhibit further clocking of timer0 when this trigger is detected... no way to "freeze" the number in the counter.... The prescaler output can be switched from the timer0 register to the watchdog timer, but if this is done, a series of instructions must be executed to prevent an accidental software "reset" of the CPU, because the watchdog timer is used to restart the CPU if there is any sort of catastrophic failure in program execution.

To deal with this problem, I used another method to "freeze" the counter... the prescaler ratio is changed from divide-by-2 to divide-by-256, ( when the calibration 1-shot generates an interrupt ) which will "slow down" the timer0 clock by a factor of 128... slow enough to ensure that the numeric value stored in timer0 does not change while it is being read by software. The prescaler ratio is restored to divide-by-2 when the next "D" bit ( 360 rollover ) pulse is detected on pin 2.


Once the startup routines are executed, software execution passes to the overflow reset routine, which loops "endlessly" unless it is interrupted. This routine watches pin 2 for a falling edge signal change, and clears the timer0 register ( and overflow bit ) when this event is detected. ( resets the bearing counter to zero )

An interrupt is generated when a falling edge signal from the calibration 1-shot is detected on pin 6. For the benefit of micro "novices", an explanation of the events immediately following the interrupt is offered :

The CPU stops execution of the "360 overflow" routine by finishing the instruction which was "in progress" at the time of the interruption. The address of the next instruction is then "memorized" so the CPU can return to the same point in the program, when the interruption is finished. This is done by saving ( "pushing" ) the address of the next instruction to be executed ( the "return" address ) into a portion of memory called "the stack". This is done automatically, by the CPU.

The CPU then proceeds to the interrupt routine, which starts at address 0004. This address is defined by the chip design, and cannot be changed. A description of the interrupt routine begins in the next paragraph. When the interrupt routine is finished, it must execute a "return" instruction, which causes the return address to be retrieved from the stack, ( "popped" from the stack ) and re-installed in the CPU. Control of the CPU is then transferred back to the instruction at that address.... the next instruction that would have been executed, if no interruption had occurred.

The interrupt subroutine begins when CPU control is automatically transferred to the subroutine starting at address 0004. The software then "saves" the contents of the important CPU registers in some "temporary storage" registers, so the CPU registers can be restored to their original condition when the interrupt is completed. ( This "restoration" is done immediately before the RETURN instruction is executed, at the end of the interrupt subroutine ) Following this, the prescaler ratio is changed from divide-by-2 to divide-by-256, for reasons previously explained.

Once the prescaler ratio has been changed, ( to "freeze" the timer0 number ) the interrupt execution is handed off to a "strobe" routine, which generates a brief pulse on PIC pin 9, once for every 7 ( RS232 ) message "transmissions". This is done for the benefit of the digital readout, to reduce "jitter" in the numeric display... one pulse for every 7 messages equates ( approx. ) to 2.5 display "updates" / second.

After this, execution proceeds to a "bit testing" routine, which ( conditionally ) calls a BCD summing routine. The bit testing routine checks each individual bit of the 9-bit binary number... if the bit is clear, ( logic 0 ) it is ignored, and testing proceeds to the next bit. If the bit is set, ( logic 1 ) the corresponding 3 digits of BCD data ( for that particular bit ) are loaded into a temporary ( 3 byte ) BCD buffer, and the summing subroutine routine is called.

The summing subroutine adds the contents of the 3 BCD temporary registers to the total BCD value which has "accumulated" up to that point, and the subroutine then returns to the calling ( bit test ) routine. When the last bit has been tested, ( and summed if the bit = 1 ) the accumulated result in the 3 BCD "total" registers represent the signal bearing, in BCD form.

From here, interrupt execution is handed off to a BCD - to ASCII routine, which simply adds ( hex ) "30" to each BCD digit, to convert each digit to its corresponding ASCII 8 bit character. Execution is then handed off to a top-level routine which loads each BCD digit into an output buffer, ( along with other characters ) and then calls an output subroutine. The output routine is called 7 times... once for the "%" character, which signals the start of the message, 3 times for the BCD number, ( MSD transmitted first ) once for the "/" character, once for an ASCII "7", ( = signal quality from 0 to 7 ) and once more for the "carriage return", which terminates the message.

The output subroutine tests each individual bit of each ASCII character, and calls one of two different subroutines, based on test results... One subroutine generates a logic "1" output, and one "bit" of ( software generated ) time delay, at the desired baud rate. The other subroutine generates a logic "0" output, and one "bit" of ( software generated ) time delay, at the desired baud rate.

If you try to analyze the software "step by step", please note that the "start" and "stop" bits employ POSITIVE logic, but the data bits employs NEGATIVE logic. Also, the polarity of the PIC output bit is reversed by the PNP transistor... a positive logic 1 ( = +5V ) on the PIC output pin will yield a positive logic 0 ( = -12V ) at the RS232 output.

The baud rate generator uses software timing loops, which are sensitive to the crystal operating frequency... this software assumes the crystal frequency is 3.58 MHz... a "color burst" crystal... if another crystal frequency is used, the timing values in the software delay loop must be adjusted accordingly, to achieve the proper baud rate.

Following transmission of the message terminator, ( "CR" ) the interrupt routine restores the status of the CPU registers, sets the SKIP flag, ( explanation below ) and executes a RETURN instruction. This will NOT re-enable the calibration 1-shot interrupt, for reasons explained below :


During the time interval when the message was being transmitted, the timer0 bearing counter has continued "accumulating" pulses from the X720 clock, because this is achieved in hardware. The "360 rollover" function ( = bearing reset ) has NOT been active during this time, because it is achieved in software. Therefore, the number stored in the bearing counter will not be accurate, ( after a message transmission ) and might even exceed 360 degrees.

When the interrupt is completed, execution returns to the main program, which is the 360 rollover routine. If another interrupt occurs BEFORE the 360 rollover routine "clears out" the old / faulty bearing data, the next transmission will contain faulty data. For this reason, the interrupts ( which start the transmission process ) remain disabled UNTIL the 360 rollover routine detects another 360 rollover, and clears out the old data.

NOTE : During the software debugging process, I found it necessary to add some lines of code that examines a status "flag" ( called SKIP ) to see if the 360 rollover routine had been previously interrupted. If so, the "falling edge" signal that was ( most recently ) detected by the rollover routine might be a "false alarm", and should be ignored... the SKIP flag is then cleared, and the 360 rollover routine starts looking for another 360 rollover.... in other words, it "skips" one of the 360 rollover pulses.

The SKIP flag is set in the final lines of code for the interrupt routine, just before the interrupt routine executes a RETURN instruction. It is used to provide a "signal" to the 360 rollover routine, indicating that it has just been interrupted.

I had to "contrive" this status flag ( and use it ) because it appears that the code lines which were intended to restore the CPU and STATUS registers to their original condition ( before returning from the interrupt ) simply weren't doing a complete job... the 360 rollover routine employs some "bit test" instructions, and the results of these bit tests are ( apparently ) stored in some invisible / secret part of the CPU which cannot be accessed, and ( therefore ) cannot be restored to their original condition before returning from an interrupt.

As a result, I found that the RS232 messages sometimes contained "nonsense" bearings, which were always ( numerically ) smaller than the real / true bearings, because the 360 rollover routine was clearing the bearing counter prematurely. If a CALIB 1-shot pulse was detected after this event, ( and before the 360 rollover routine detected another rollover pulse ) then a "false bearing" was transmitted.

Anyway, that's what I THINK was happening... the problem vanished when I added the SKIP flag.

Ain't software grand...?