The "CT" MedFER Beacon
A PIC-Based MedFER beacon operating PSK31 or FSK31

"About source code Version 2.0"
also, look at the Version 2.0x User's guide page.
(For info about Version 1.0x, go here)

This page is updated as the software evolves:  Check back again occasionally...

What is on this page:

NOTE:  The section Software Reliability was added on 28 February, 2001.  Please read this (and its references) if you plan to use this for generating telemetry where the transmitter may be "out of reach."

As of 7 November, 2000, the software was updated to greatly improve intermod readings.  Go here for more details.

This page gives a brief overview of the internal workings of the source code, as well as some of the (il)logic behind its design and implementation.  Links to the source code are also provided.  A .HEX file also included for those who do not have a C compiler but wish to program their own processor.

(And yes, this page does look suspiciously like the one for describing version 1.0.)

A few words about the source code:

The discussion of Version 1.0 may be found here.

The source code for this is written in a combination of assembly and C (using the CCS PICC compiler.)  I have used this compiler for several years now and have found that, once mastered, it can produce very efficient code that rivals hand-tweaked assembly- but, when faced with the need for optimum speed and compactness, I will sometimes insert assembly.  This code, however, wasn't excessively tweaked:  It has been written more for readability than efficiency.

As mentioned above, it is compiled with the CCS "PCW" C compiler (their web page may be found at but even if you don't have the compiler, the source code should provide insight as to how it all works.

There are TWO versions of the source code -  Version 1.0x is discussed here:

"Byte-by-byte" description of the source code files:

Just below the "documentation commentary" at the beginning of the source file is a definition of a constant named "16C84".  If this variable is defined (i.e. the comments at the beginning of that line of code are removed) then code appropriate for use with a PIC16C84 is generated.  If the "16C84" variable is undefined then code for the PIC16F84 is generated.

The source code for the PIC-based PSK31 transmitter controller consists of two source code files:  The main body of the source code, and a header file called "varicode.h" that contains the bit patterns for the varicode words.  The "varicode.h" file is the same for the two version  Note that only bytes 0-127 are supported in this implementation (but it would be very easy to expand that to the full 256 byte set if one accepted the penalty of additional ROM usage.)

As with most any C program, one declares variables near the top of the code:  This is no different.  As a quick note, "byte" variables are the same as "unsigned char" which are the same is "char" variables (i.e. there are no signed variables) while "bit" and "short" (both identical) represent a single bit of memory.  In the case of either bits or bytes, either RAM or Program registers (the same thing on the PIC...) may be represented.  In addition to definitions, there are various constants, masks, and hardware settings.

Also defined are tables containing the modulation waveforms.  In this case, there are two tables:  sintbl (really a "cosine" table) holds half a cosine wave and the pw_sine contains one entire cycle of a cosine waveform.

FSK31 operation:

At this point, it is worth mentioning that PB3 (pin 9 on the PIC) outputs a logic signal representing the current phase of the varicode.  While the original purpose of this signal was for debugging, it was left intact in the event one wished to generate the BPSK modulation envelope by another means (i.e. not using the PWM routine, etc.)  It is also useful for generating FSK31, as this signal need only be applied to a frequency-shift network.  An example of such an application may be found here.  This signal is present in both  "Balance Modulator" and "AM" modes.

The Interrupt Service Routine (ISR):

The ISR operates only during transmission of data:  It is disabled during serial port operation.

The ISR is invoked at a precise 1 KHz rate (using a 3.579545 MHz NTSC colorburst crystal) and is fine-tuned with a few NOPs.  The first order of business is to reset the ISR's hardware timer.  The next part of the ISR generates the PWM voltage.

The PWM value (which may vary from 1 to 99) is copied, and an "inverse" is calculated.  The total routine consists of (more or less) 100 "loops."  The voltage is proportional to the value held by "PWM" and is generated thusly:

The PWM output pin (PWM_OUT) is normally in a HI-Z state.  When the first portion of the PWM routine is entered, this pin is set high to charge the capacitor on the output pin.  An assembly-language loop counts "PWM" times (for a short delay.)  After this delay has finished, the PWM_OUT pin is set to low to discharge the capacitor as the next loop executes "100-PWM" times.  After this is done, the PWM output is reset to a HI-Z state.  At the finish, the capacitor contains a voltage that is proportional to the value in the PWM variable.  Note that the PWM value is preserved so that it may be re-used on the next pass through the ISR.

After the PWM is generated, a counter keeps track to see if 32 ISR cycles have elapsed (1 bit period.)  If so, it resets the "send_ok" variable to indicate to the driver routines that the current bit is done and that the next bit may be generated.

The next routines generate the PWM data.  The first one generates PWM data for the "Balanced Modulator" method and is activated by grounding pin PB4 (pin 10, the OUT_MODE pin.)  This routine works as follows:

If the bit in OUT_BIT is 1, the counter sinptr is incremented (unless it is already at a maximum count of 31.)  Conversely, if the bit is 0, the sinptr counter is decremented (unless it is already zero.)  Note that OUT_BIT has already been NRZ-I encoded by the bitsend() function and does not directly reflect a 0 or 1 bit in the varicode character.

If the OUT_MODE pin is left open, the pullup resistor on the 16x84 causes a logic HIGH and the "AM" mode is selected.  It works by looking at the varicode bit to be sent (a copy of which is kept in the mbit_send variable.) If it is a 1, then there is to be no phase shift.  If it is a 0, then the sinptr pointer is cleared to point to the beginning of the PW_SINE table and the mbit_send variable is set to a 1 to indicate that the current bit is being processed (and that we shouldn't try to process it again...)  Once the counter is cleared, it begins counting up and obtaining the PWM values for the ISR.  Once the value hits 31, the modulation sequence is finished (for that bit) and the lookup is halted.  The phase shift output in the AM mode is presented on pin B2 and this is produced by simply toggling the bit as the counter progresses through the PW_SINE table.  Note that the "flip" occurs at the 19th entry in the table instead of exactly at the "bottom" of the waveform:  This is done because the R/C filtering causes a bit of a delay that must be compensated.

Why this sequence of events?  When the ISR is entered, the first priority is to add fine-tuning delays and update counters to prepare the hardware for the next ISR call.  The PWM routine is then called, followed lastly by generating the PWM data for the next call.  If it were not done in this sequence, the varying times of the individual portions of the ISR would cause some phase modulation of the generated waveforms and/or inaccuracies in timing.

The "main()" function:

Note:  Specific information about how to "operate" this beacon may be found on the Version 2.0x User's guide page.

The "Original" EEPROM Playback mode:

At the beginning of this function, all global variables are initialized as appropriate.  Immediately upon startup, the status of pin PB5 is checked:  If it left disconnected, it will be pulled high by the PIC's internal pullup resistor indicating that the this device is to behave exactly as it did in Version 1.0:  That is, EEPROM contents are played back repeatedly with a "warble" consisting of 81 phase shifts ("zeros") being sent between message.  This allows for the intermodulation display of the receiver to work as well as providing an audible "cue" relating to the begin/end of a message cycle.

The heartbeat pin (PB7, pin 13):

An LED may be connected (with a series resistor) between this pin and ground (a push-on jumper is recommended if you wish to conserve power during battery operation) to indicate processor activity.  In all modes, this LED will flash briefly during the first bit of each character being sent.  In the serial control mode, this LED will also flash once-per-second when the PIC is idle, awaiting serial input, and it will flash during reception of each byte received over the serial port.  When in serial control mode, this HEARTBEAT may be turned on or off via software, but note that it always defaults to ON upon powerup!

The "Serial control" mode:

Once again, specific commands available via the serial port may be found on the Version 2.0x User's guide page.

If PB5 (pin 12) is grounded, then the serial input mode is indicated and only this portion of the main() loop is executed.  Serial input is "inverted" - that is, a "0" is a "1" and vice-versa.  This may seem a bit strange at first until one realizes that what comes out of a computer's RS-232 port is also inverted:  All that is required to connect the PIC directly to an RS-232 port is a series resistor to limit current into the serial input pin, PB6 (pin 12.)  For unipolar inputs (i.e. 0-12 volts) a 2.7k resistor is appropriate, for bipolar inputs (i.e. -12 to +12 volts) up to 10k may be used.  If you are driving this input directly from another device that runs from the same supply as PIC, then the resistor may be omitted completely.  It should be noted that PB6 has an internal pullup resistor:  If another "inversion" of the data is required, a simple NPN transistor circuit may be used, using the internal pullup only.

The serial input operates at 1200 baud, 8 bits, 1 or 2 stop bits, and no parity.  Internally, the MSB is ignored, limiting the input characters to the range of 0-127 (decimal.)  Additionally, when the beacon is transmitting, the serial input is completely ignored.  For "handshaking" indication, one may observe the status of the PTT_OUT pin (PB0) - if it is low, then the serial data input is available.

The first thing that happens is that the first byte (at address 0) of the EEPROM is checked:  If the MSB (Most Significant Bit) is set, then we know that we are supposed to repeat EEPROM contents:  A single-bit flag, eeprom_playback, is set to indicate this mode.

Following checking the MSB, important variables and flag bits are set to their initial states to make sure pointers are in the right place, flags are reset, etc.

One of these flags, msg_complete, indicates whether or not the message is currently being built up. If it is clear, we enter another sub-loop and wait for input.  While it is waiting for some sort of input, the heartbeat LED is flashed (if enabled.)  Note that some of the variables from the ISR are "stolen" for this routine:  This is OK, since the interrupt is disabled at this point.  The approximate duty cycle of the LED is 1 flash every second, with the flash lasting approximately 4 milliseconds.  This period flash indicates that the PIC is awaiting input from the serial port.

Message "Preamble" and "Tail":

Any message sent with PB5 grounded begins with a "preamble" and ends with a short "tail."

The "preamble" consists of a "warble" of 81 "zeros" being sent  (a zero being represented in PSK31 by a phase shift.)  This preamble performs several functions:  Most importantly it allows the receiver to synchronize to the bitstream, it allows the IMD (intermod) reading to be displayed by the receiver, and it provides a listener a distinctive audible "cue" of the beginning of a transmission.

The "tail" consists of 750 milliseconds (3/4 of a second) of unmodulated carrier.  This allows the squelch of the receiver to close gracefully at the end of a transmission, hopefully reducing the possibility of "garbage" characters.  Like the "warble" of the preamble, it, too, has a very distinctive sound and provides another audible "cue" of the ending of the transmission.

The "Serial-initiated beacon" mode:

Embedded within heartbeat this subroutine is the eeprom playback routine.  This takes advantage of the delay built into the heartbeat routine to allow about 1 second for the controlling computer to send a command (via the serial port) to disable this mode.  If this mode is enabled (i.e. the eeprom_playback variable is set) a cycle is begun which proceeds as follows:

Serial input mode:

If the eeprom playback routine is not enabled, we look at the input characters to determine whether or not it is a valid control character, OR something that may be transmitted.  If it is one of the several control-character based commands, these will be acted upon:  If it is not one of these commands, then the control character will simply be passed through.

If a control code is sent to indicate that the message is "completed" (and the message_complete flag is set) and the buffer contents may be sent.  As in the eeprom playback mode, the PTT_OUT and KEY_OSC pins are made active, the interrupts are re-enabled, and the RAM contents are sent, optionally followed by EEPROM contents.  The 2.5 second "preamble" warble is sent prior to the transmission, and 750 millisecons of "dead carrier" follows all of the message.

A few words about the various control commands:

Several commands are available.  The operations of these are detailed on the Version 2.0x User's guide page.  The source code's documentation should provide someone interested with more information.

Software reliability:

Every reasonable effort has been made to design the software so that unexpected inputs will not cause the PIC's program to crash.  More likely than not, if the PIC crashes, it will be due to some sort of power supply anomaly.  One of the attempts made to improve software reliability is to use the PIC's onboard watchdog timer.  As implemented, the watchdog timer requires a reset approximately every 18 milliseconds.  If the software does not do this, a hardware RESET occurs.

For more information, it is highly recommended that you read the Version 2.0x User's guide - Software Reliability section.

The Version 2.0x source code and .HEX files:

First, the fine print:

This source code copyright 1999 and 2000 by Clint Turner.  It may be freely used for non-commercial purposes on provision that credit is given to the source.  There are no warranties expressed or implied.  Suitability of this source code and its application is to be determined by the user.  Federal law prohibits use of this product in a manner inconsistant with its labeling. Not all options available on all models.  Batteries not included.  Do not taunt "Happy Fun Ball."  Your mileage may vary.  Substantial penalties for early withdrawl of funds.  Past performance should not be construed as a guarantee of future performance.

Software Update:

As of 7 November, 2000, version 2.04 was released.  This greatly improves the intermod readings - especially in the "AM" mode.  This is designated with the filenames psk31f4.*.

Now, here are the source code files:

psk31f4.c - The actual version 2.04 source code in C and assembly using the CCS PIC C compiler
varicode.h - A header file containing the PSK31 varicode for characters 0-127 decimal.

The .HEX files:

The file psk31f4c.hex has been compiled for use with a PIC16C84 and the psk31f4f.hex had been compiled for the PIC16F84.  These HEX files are suitable for importing into MPLAB (available from the Microchip website at and burning into the processor.

A default EEPROM message is included.

WARNING:  You will want to customize the message to suit your tastes.  As provided, the following text will be sent:

Your message goes here. It may contain up to 64 characters.

Not very exciting, huh...  Read below to find out to make it say what you want it to say.


Hopefully, between source code documentation and this description, you can make some sense out of it all.  A few things to note, in case you didn't read the above very carefully (or if I just confused you:)

For additional links/information on PSK31, MedFER, and LowFER operation, refer to those at the bottom of the PSK MedFER page.

Return to the PSK MedFER page

Any comments or questions?  Send an email!

This page copyright 1999 and 2000 and maintained by Clint Turner, KA7OEI.  Updated 20061103

Since 12/2010: