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

"About the source code Version 1.0x..."
(For info about Version 2.0x, go here)

What is on this page:

The software was updated on 7 November, 2000.  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.

A few words about the source code:

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, 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 appropriate.

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 (discussed below) and Version 2.0x (discussed here):

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

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, 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 pin B3 outputs 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 the  "Balanced Modulator" and "AM" modes.

The Interrupt Service Routine (ISR):

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 (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:

Skipping down to the bottom of the source code, there is the main() function.  After the hardware is intialized, an endless loop is entered.  The first order of business is to generate a message "preamble" consisting of 81 "zeros" (phase shifts) to cause a program like PSK31SBW to display an accurate intermod reading.

After the preamble, the EEPROM message is started.  This message may use all 64 bytes of EEPROM memory, or it can be shorter, terminated (and thus repeated) by a 0xff signifying end-of-message.

After the character to be sent is retrieved from the EEPROM, it is sent to the lookup_varichar() function where the varicode word is indexed and retreived.  A flag is now set indicating that "character sending" is in progress and the sendbit() function is invoked.  This function shifts out the current varicode bit and determines if this was the last bit of the varicode word, or if there other varicode bits to be sent.  The processor then calls the bitsend() function and waits either for the current bit to be completed, or does the NRZ-M conversion of the current bit and interfaces with the ISR.

The source code:

First, the fine print:

This source code copyright 1999/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, the source code was updated to version 1.01 (file name:   psk31e3.*)  In this version, a minor bug was fixed that prevented the 81 zeroes (the "warble") from being sent between message repeats when the EEPROM message was exactly 64 bytes.  Also, the "sine" tables were fixed, greatly improving intermod reports.

Now, here are the source code files:

psk31e3.c The actual version 1.01 source code in C and assembly using the CCS PIC C compiler
varicode.h A header file containing the PSK31 varicode

And here is a compiled .HEX file intended for the 16C84 or 16F84:

psk31e3.hex  A HEX file suitable for importing into MPLAB (available from the Microchip website at and burning into the processor.

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: