A Few Bits of Code

BITX20 Clone

A 20m SSB transciever that operates between 14.000 - 14.350MHz. It uses a DDS as an oscillator to synthesize the required HF signals.


This HF transceiver is an amalgamation and clone of the fantastic units designed by Ashhar Farhan. His original designs can be found at http://www.phonestack.com/farhan/ and http://www.hfsigs.com/. My BITX20 Clone is a SSB transceiver operating between 14.000 - 14.350MHz. It uses the design of the newer 40m BITX40 amplifiers, but operating in the 20m range. It also obviously keeps the LPF and band-pass filters from the BITX20.


The hardware for this is a direct clone of the BITX40, but using the Low Pass Filter (LPF) and Band Pass Filter (BPF) from the BITX20 design. Since I built this project as a way to better understand RF, I built each module of the schematic separatly so they could be tested individually. This ended up being quite a bad idea as there seems to be some grounding issues causing static in the transmit/recieve signal path. Also, the digital and analog grounds seem to be interfering with each other in some way causing noise from the LCD and arduino to couple into the output of the audio signal.

Below are the original schematics from Ashhar Farhan's site. I have modified them to add labels to the different sections of the unit for the RF layman.

Download Full-Size

I use LTSpice to simulate how the filters will perform with components that I have available. Listed here are the schematics and simulations for the BPF and LPF used:


Older versions of the BITX are 100% analog with an analog VFO. The newer versions (like this one) use a Direct Digital Synthesizer (DDS) to generate the necessary HF signals to tune the transiever. The DDS is controlled by a microcontroller. We can also use the Microcontroller to control an LCD and a rotary encoder. In this case we use an Arduino as the microcontroller. Below is the source code used:

//#include <EEPROM.h>         // Store user settings
#include <Wire.h>           // I2C
#include <si5351.h>         // Si5351
#include <MD_KeySwitch.h>   // Switch Debounce
#include <RBD_Button.h>
#include <Encoder.h>        // Rotary Encoder
#include <LiquidCrystal.h>  // LCD display

// Max/Min Frequencies we want to handle
#define FREQ_MAX 14350000
#define FREQ_MIN 14000000

// The BFO frequency for our board
#define FREQ_BFO 10000000

#define  KEY_DEBOUNCE_TIME   150  // in milliseconds
// Setup inputs and the pins they are connected to
MD_KeySwitch f1Btn(A1);
MD_KeySwitch f2Btn(A2);
RBD::Button pttBtn(12);
LiquidCrystal lcd(9, 8, 7, 6, 5, 4);  //LiquidCrystal(rs, enable, d4, d5, d6, d7) 
Encoder encoder(2, 3);  // Must use pins 2/3 because they are the interrupt pins
Si5351 si5351;

int32_t oldPosition = 0;  // Holds the last position of the encoder
uint32_t displayFreq = 14200000;  // Initial frequency

#define NUM_CURSOR_POS 6
// Position 1 = 1 Hz, Position 2 = 10Hz, Position 4 = 1 KHz, etc.
uint8_t cursorPos = 3;  // Current position of the cursor

void setup()
//  Serial.begin(9600);
  // Setup Si5351
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 27000000UL, 0);
  si5351.set_correction(-3500);   // Correction factor for my individual Si5351 chip
  //TODO: Allow user to change the correction factor

  // Setup Si5351 Outputs
  si5351.output_enable(SI5351_CLK0, 0);   // Disable unused outputs
  si5351.output_enable(SI5351_CLK1, 1);
  si5351.output_enable(SI5351_CLK2, 1);   // Setup inital output on our used output
  si5351.set_freq( (displayFreq - FREQ_BFO) * SI5351_FREQ_MULT, SI5351_CLK2);

  //DEBUG - Testing Si5351 Output @ 10MHz
  si5351.set_freq( 10000000 * SI5351_FREQ_MULT, SI5351_CLK1);
  // Setup cursor buttons to change tuning range (cursor position)


  // Setup LCD
	lcd.begin(16, 2);     // Init the LCD
	lcd.print(displayFreq); // Initial display
  //lcd.cursor();         // Turn on the cursor

  // Setup relay outputs
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);

  // FAILSAFE - Set transciever/relays to recieve mode 
  // (resetting power resets the realys in case something goes horribly wrong)
  digitalWrite(11, HIGH);
  digitalWrite(11, LOW);

void updateFreq( int32_t stepSize )
  // Use cursor position to get actual step size
  // based on which digit changed in the UI
  displayFreq += pow(10, cursorPos-1) * stepSize;

  // Check for min/max limit
  if( displayFreq > FREQ_MAX ) {
    displayFreq = FREQ_MAX;
  if( displayFreq < FREQ_MIN ) {
    displayFreq = FREQ_MIN;

  // Update the Si5351 freq synth.
  si5351.set_freq( (displayFreq - FREQ_BFO) * SI5351_FREQ_MULT, SI5351_CLK2);

void loop()
  // Check if any of the function buttons have been pressed
  if( f1Btn.read() == MD_KeySwitch::KS_PRESS ) {
    if( cursorPos > NUM_CURSOR_POS ) {
      cursorPos = 1;
  if( f2Btn.read() == MD_KeySwitch::KS_PRESS ) {
    if( cursorPos < 1 ) {
      cursorPos = NUM_CURSOR_POS;

  // Check PTT
  if(pttBtn.onPressed()) {
    digitalWrite(10, HIGH);
    digitalWrite(10, LOW);
  if(pttBtn.onReleased()) {
    digitalWrite(11, HIGH);
    digitalWrite(11, LOW);
  // Read the encoder's position
  // Divide by 4 to only count whole steps/indents of the encoder
  // Invert so that turning CW increases the value and CCW decreases it
  int32_t newPosition = -encoder.read()/4;
  //TODO: Check for min/max position on encoder counter (and reset/correct it)

  // Update frequency when encoder is turned
  if( newPosition != oldPosition ) {
    oldPosition = newPosition;

  //DEBUG - Display seconds since boot
	//lcd.setCursor(0, 1);	// Set the cursor on col 0, line 1 (second row)
	//lcd.print(millis()/1000);	// Display seconds since reset

  // Display UI cursor
  // cursorPos counts from the left so (NUM_CURSOR_POS - cursorPos) 
  // is the offset from the left. -1 more to get the offset from 0
  //lcd.setCursor(NUM_CURSOR_POS - cursorPos - 1, 0);


Download Source (4.35 Kb)


Here are some pictures of the current unit. You can see the shell is opened like a book with part on both the metal base and plastic cover. The power supply and power amplifier are on the upper left. They are separated from the more sensitive amplifiers on the right with a grounded partition. The Arduino, LCD, and speaker are all attached to the top cover.

This prototype was also built using "manhattan style" construction which uses small squares of pcb to create standoffs above a common ground plane. The legs of each component are then used to make connection between the standoffs.

All of the sections of the transiever were built on separate PCBs. As I will explain below, this may be a hugh problem in terms of static noise that is getting into the system somewhere. Each board is as follows: (top row, left to right) Power Amp, LPF/Relay board, Relay controller, 1st IF Amp, Diode Mixer, 2nd IF Amp, Crystal Filter, 3rd IF Amp, Balanced Modulator/Detector, (bottom row, right to left) Audio/Mic Amps, VFO Amp, DDS, BPF, Power Supply

The Arduino Pro Mini is a cheap $4 clone from Micro Center. Most other parts were ordered from Arrow.com. Some things were scavenged. The speaker is actually two speakers salvaged from an old laptop. They work well at reproducing the bassy male voices you tend to hear when on the 20m band.

This current prototype has a large amount of static noise during both transmit and recieve. I believe it stems from a grounding issue with the prototype. I am currently tring to fix the problem, but Electrical Engineering is not my expertise and the troubleshooting is currently moving at a snail's pace.


I created this before the uBITX was announced which is even more reliant on the DDS to implement multiple IF filters, as well as do away with all the other crystal oscilator sections. This was the same plan that I had for this project, but I was beaten to the punch so to speak. For now, this project is focused on interfacing with a computer so that I can operate in the various digital modes available to a HAM operator.

I would also like to better tune the BPF. You can only add or remove a full turn at a time on inductors. Therefore, I would need to use variable capacitors to get the frequency cut-off just right.