Our homebrew repeaters consist of two commercial radios, a duplexer, a power supply and a homebrew controller. The controller itself consists of two PCBs which plug into a backplane. One PCB is the logic board. It decides when to key up the transmitter, when to issue automatic repeater IDs and many more things that are within the realm of the controller logic. The second PCB is the audio board. Among other things, it is responsible for filtering the audio from the receiver before it is sent back to the transmitter, summing up this audio with the CW tone, controlling an mp3 module for the automatic voice ID and synthesizing the PL/CTCSS tone which is also added to the audio sent to the transmitter.

In order to perform these tasks, an auxiliary PIC16F722A [datasheet] microcontroller is used. This chip is strapped to the I2C bus, available at the backplane, to receive commands from the logic PCB. In particular, the following features are implemented:

  1. Synthesizing a PL tone by means of a DDS.
  2. Setting output pins HIGH momentarily. The pins will fall back to LOW after a fixed hold time.
  3. Setting output pins to a HIGH or LOW state.

The first feature is arguably the most interesting aspect of this solution. The second feature is used to control the ELV MSM 2 mp3 module and the third feature is used to mute audio paths by grounding them through a MOSFET. It is possible to use the software published on this site for the DDS only, ignoring or disabling the remaining features.

We are powering the microcontroller from a 5 V rail, although operation on a 3.3 V level is equally possible. Naturally this will result in a lower DDS output level. The 7-bit I2C address is set to 0x39, but can be easily changed in the code. While the internal 16 MHz oscillator can be used, we added an external 16 MHz crystal oscillator to mitigate drift originating from temperature changes which are quite significant at some repeater sites. If you want to use the internal oscillator instead, you have to change #pragma config FOSC in the config_bits.h file.

DDS Implementation

The working principle of a DDS is explained in quite some detail in this video. Instead of an R2R ladder, a PWM output is used in the solution described here. The following figure illustrates this concept.

Pulsed DDS principle

The top plot shows the sinewave that is to be synthesized. The little red dots are the sampling points; they correspond to the period of the square wave in the second plot. The duty cycle of the square wave is altered such that the average over a period equals the sampled value, i.e. the red dot in the top plot. The averaging is performed by a reconstruction filter which is discussed below.

DDS Specifications

The following table summarizes the characteristics of our DDS:

DDS clock15625 Hz
PWM resolution8 bits
LUT size256 bytes
LUT width8 bits
Frequency resolutionapprox. 238.4 mHz
Worst absolute CTCSS frequency error0.118 Hz (at 114.8 Hz)
Worst relative CTCSS frequency error0.142 % (at 71.9 Hz)

Last but not least, modifying the source code to support additional frequencies is trivial: The phase increment can be computed by dividing the desired output frequency by the DDS frequency resolution. The frequency resolution in Hz is fΔ=15625 Hz216; the phase increment is therefore φincrement=fdesiredfΔ.

Reconstruction Filter

A passive RC filter is used in our design to integrate the square wave into a sine wave. Some more considerations with respect to the filtering of DDS and PWM signals can be found in this video.

Our filter is shown below, although it must be said that there might be better approaches to this than what is depicted. Then again, this solution fully satisfies our needs.

Three-stage passive RC filter and inverting OpAmp circuit.

As this is not a split-supply design, V_half is the half-rail voltage generated by another OpAmp. R220 can be used to further increase the gain of the inverting amplifier that follows the filter. We designed this in just in case, but populated the footprint with a 0 Ohm jumper. Using an inverting amplifier here allows for the option of negative gain. The purpose of R230 is just to improve the cross-over distortion behaviour of the good old LM358 and can be omitted if a better OpAmp is used.

In addition to averaging out the pulses, it is this filter's job to reject unwanted nyquist images. Those images will appear close to the sampling frequency of 15625 Hz, hence the requirements on the filter in this regard are fairly low. If necessary, the passive filter could be optimized such that the later stages impose less loading on the early stages. Probably the number of stages can be reduced anyway. An active filter could be used instead (e.g. a Sallen-Key filter), but it is generally a bad idea to introduce gain there. A second OpAmp would be needed as a consequence.

I2C Register Map

The following table shows all the commands that are understood by the aux_pic. The microcontroller acts as an I2C slave and expects one command byte after the address. Hence, the I2C transaction executed by the I2C master is:

  1. Send START condition.
  2. Send 7-bit address as most significant bits. The least significant bit is 0 to indictate a write operation.
  3. Send command byte.
  4. Send STOP condition.
Command ByteDescription
0b0xxxxxxxSet PL tone. See the table below as well as pl_tone_t_enum.h for a list of supported PL tones and their corresponding command byte. The PWM waveform is available on pin RC2.
0b10000001Momentarily set pin RA0 HIGH.
0b10000010Momentarily set pin RA1 HIGH.
0b10000011Momentarily set pin RA2 HIGH.
0b10000100Momentarily set pin RA3 HIGH.
0b10000101Momentarily set pin RA4 HIGH.
0b10000110Momentarily set pin RB1 HIGH.
0b10000111Momentarily set pin RB2 HIGH.
0b10001000Momentarily set pin RB3 HIGH.
0b10001001Momentarily set pin RB4 HIGH.
0b10001010Momentarily set pin RB5 HIGH.
0b11000000Set pin RC6 LOW.
0b11000001Set pin RC6 HIGH.
0b11000010Set pin RC5 LOW.
0b11000011Set pin RC5 HIGH.

Output Frequencies, Errors and I2C Commands

The following table shows the pre-programmed output frequencies along with their I2C command bytes, the macros defined in pl_tone_t_enum.h as well as the actual output frequencies along with errors. These are calculated figures of the DDS alone. Errors from the reference clock are not accounted for.

Macro Command Byte Desired Frequency Actual Frequency Absolute Error Relative Error
PL_06700b0000000167.0 Hz66.996 Hz0.004 Hz0.007%
PL_07190b0000001071.9 Hz72.002 Hz-0.102 Hz-0.142%
PL_07440b0000001174.4 Hz74.387 Hz0.013 Hz0.018%
PL_07700b0000010077.0 Hz77.009 Hz-0.009 Hz-0.012%
PL_07970b0000010179.7 Hz79.632 Hz0.068 Hz0.086%
PL_08250b0000011082.5 Hz82.493 Hz0.007 Hz0.009%
PL_08540b0000011185.4 Hz85.354 Hz0.046 Hz0.054%
PL_08850b0000100088.5 Hz88.453 Hz0.047 Hz0.053%
PL_09150b0000100191.5 Hz91.553 Hz-0.053 Hz-0.058%
PL_09480b0000101094.8 Hz94.891 Hz-0.091 Hz-0.096%
PL_09740b0000101197.4 Hz97.513 Hz-0.113 Hz-0.116%
PL_10000b00001100100.0 Hz99.897 Hz0.103 Hz0.103%
PL_10350b00001101103.5 Hz103.474 Hz0.026 Hz0.025%
PL_10720b00001110107.2 Hz107.288 Hz-0.088 Hz-0.082%
PL_11090b00001111110.9 Hz110.865 Hz0.035 Hz0.032%
PL_11480b00010000114.8 Hz114.918 Hz-0.118 Hz-0.103%
PL_11880b00010001118.8 Hz118.732 Hz0.068 Hz0.057%
PL_12300b00010010123.0 Hz123.024 Hz-0.024 Hz-0.020%
PL_12730b00010011127.3 Hz127.316 Hz-0.016 Hz-0.012%
PL_13180b00010100131.8 Hz131.845 Hz-0.045 Hz-0.035%
PL_13650b00010101136.5 Hz136.614 Hz-0.114 Hz-0.083%
PL_14130b00010110141.3 Hz141.382 Hz-0.082 Hz-0.058%
PL_14620b00010111146.2 Hz146.151 Hz0.049 Hz0.034%
PL_15140b00011000151.4 Hz151.396 Hz0.004 Hz0.003%
PL_15670b00011001156.7 Hz156.641 Hz0.059 Hz0.038%
PL_16220b00011010162.2 Hz162.125 Hz0.075 Hz0.046%
PL_16790b00011011167.9 Hz167.847 Hz0.053 Hz0.032%
PL_17380b00011100173.8 Hz173.807 Hz-0.007 Hz-0.004%
PL_17990b00011101179.9 Hz180.006 Hz-0.106 Hz-0.059%
PL_18620b00011110186.2 Hz186.205 Hz-0.005 Hz-0.003%
PL_19280b00011111192.8 Hz192.881 Hz-0.081 Hz-0.042%
PL_20350b00100000203.5 Hz203.609 Hz-0.109 Hz-0.054%
PL_20650b00100001206.5 Hz206.470 Hz0.030 Hz0.014%
PL_21070b00100010210.7 Hz210.762 Hz-0.062 Hz-0.029%
PL_21810b00100011218.1 Hz218.153 Hz-0.053 Hz-0.024%
PL_22570b00100100225.7 Hz225.782 Hz-0.082 Hz-0.037%
PL_23360b00100101233.6 Hz233.650 Hz-0.050 Hz-0.021%
PL_24180b00100110241.8 Hz241.756 Hz0.044 Hz0.018%
PL_25030b00100111250.3 Hz250.340 Hz-0.040 Hz-0.016%

License, Code and Build Environment

The project is released on a github page under the GNU GPL 2 license. The code is written in C using MPLAB X (version 3.05) and builds with the MPLAB XC8 compiler.