Arduino: 1-Watt 2m Transmitter with RF Signal Generator

Since I do a lot of RF projects, I wanted to see if I could make a 2m power amplifier using a minimal set of components. I have a number of BS170 MOSFET transistors that I’ve used for dozens of applications over the years from guitar pedal pre-amps to digital control circuits. I started this project by building a class A amplifier on a bare piece of copper laminate. I was able to get 9 dB of gain with a power output of 15 dBm and a quiescent current of 83 mA. The schematic is shown below. R3 and R4 can be omitted if you happen to be building the circuit and replaced with 50 ohm SMA connectors. I used a variable cap at C3 to tune the circuit for maximum output power,

With 15 dBm of output power, I thought this would be adequate to drive a power amplifier stage. I nabbed some 2SC1970 transistors off eBay for a few bucks and started experimenting with the application circuit in the datasheet. This transistor is quite old by modern standards, but they were cheap and in a TO-220 package, so I figured they could take a beating. The standard application circuit was a good jumping off point for starting the design, and I was able to easily make changes to the air-core inductors by compressing and stretching the windings. I wound up just getting as close as I could with the inductors and relying on the variable capacitors in the circuit to get as much power as I could.

The LO portion of the transmitter was an Si5351 clock signal generator controlled by an ATMega2560 Arduino board. It’s able to get up to 160 MHz (200 MHz now in the Si5351B chips) and is programmed via an I2C port. I setup button 5 (Select) on the LCD shield to enable the LO later on, but I just had it running originally at startup and then connected the straight key in series with the 12V supply (widow-maker style) to the PA.

Above is the measured output on the old Tektronix TDS 360 oscilloscope. Measured power output is right at 1 watt. The second harmonic is a little higher than I would have liked, but an added LPF to the output should help fix that right up. The last thing to do was to get out in the rain in January and see if someone could pick it up. I hooked up a 3-element yagi to the output and the signal from the transmitter was picked up by W7YOZ in Shelton, WA some 22 miles to the north east.

Of course, here is the Arduino code for the RF signal generator using the Adafruit Si5351 clock generator board. The controls need to be incorporated into an interrupt trigger to make the thing more controllable, but it hasn’t been so annoying that I’ve needed to fix it as of yet. Just fair warning in case someone out there in the world decides to use it for anything.

// Arduino RF Signal Generator
// author: Abram Morphew
// date: 2019.04.25

#include <Wire.h>
#include <Adafruit_SI5351.h>
#include <LiquidCrystal.h>

Adafruit_SI5351 clockgen = Adafruit_SI5351();
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

long frequency = 144100000;
long inc = 1000;
char* data = "0";
int btn = 0;
bool keyPress = false;
bool enable = false;

void setup() {
  lcd.begin(16,2);
  lcd.print("Initialize");
  delay(500);
  
  /* Initialise the sensor */
  if (clockgen.begin() != ERROR_NONE) {
    lcd.print("Ooops, no Si5351 detected ... Check your wiring or I2C ADDR!");
    while(1);
  }

  setfrequency(frequency);
  delay(100);
  lcd.clear();
  
}

void loop() {
  if (keyPress == true) {
    setfrequency(frequency);
    keyPress = false;
  }
  
  // set title:
  lcd.setCursor(0,0);
  lcd.print("Frequency: ");

  // update frequency 
  lcd.setCursor(0, 1);
  lcd.print(frequency/1e6,3);
  lcd.print(" MHz ");

  btn = readkeypad();
  if ((btn == 1) &amp;&amp; (frequency + inc &lt;= 155e6)) { frequency = frequency + inc; keyPress = true; delay(100); }
  if ((btn == 2) &amp;&amp; (frequency - inc &gt;= 420e3)) { frequency = frequency - inc; keyPress = true; delay(100); }

  if ((btn == 3) &amp;&amp; (inc &lt; 1e8)) { inc = inc * 10; delay(100); }
  if ((btn == 4) &amp;&amp; (inc &gt; 1e4)) { inc = inc / 10; delay(100); } 
  if (btn == 5) {
    clockgen.enableOutputs(true);
  }  
  
  if (btn != 5) {
    clockgen.enableOutputs(false);
  }
  
  delay(10);
}

int readkeypad(){
      int adc_key_in = analogRead(0); //
      int ret = 0;

      if (adc_key_in &lt; 50) ret = 4;
      if ( (adc_key_in &gt; 800) &amp;&amp; (adc_key_in &lt; 1150)) ret = 0;
      if ( (adc_key_in &gt;  80) &amp;&amp; (adc_key_in &lt; 150) ) ret = 1;
      if ( (adc_key_in &gt; 250) &amp;&amp; (adc_key_in &lt; 350) ) ret = 2;
      if ( (adc_key_in &gt; 400) &amp;&amp; (adc_key_in &lt; 500) ) ret = 3;
      if ( (adc_key_in &gt; 500) &amp;&amp; (adc_key_in &lt; 800) ) ret = 5;
  
      
      //lcd.print(adc_key_in);
      return ret;
 }

int setfrequency(long frequency) {
  int m = 0;
  int n = 0;
  int fs = 8;
  clockgen.enableOutputs(false);

  if (frequency &gt; 55e6) {
    fs = 8;
  } else if ((frequency &lt;= 55e6) &amp;&amp; (frequency &gt; 25e6)) {
    fs = 16;
  } else if ((frequency &lt;= 25e6) &amp;&amp; (frequency &gt; 10e6)) {
    fs = 32;
  } else if ((frequency &lt;= 10e6) &amp;&amp; (frequency &gt; 6e6)) {
    fs = 64;
  } else if ((frequency &lt;= 6e6) &amp;&amp; (frequency &gt; 3e6)) {
    fs = 128;
  } else if ((frequency &lt;= 6e6) &amp;&amp; (frequency &gt; 2.1e6)) {
    fs = 256;
  } else if ((frequency &lt;= 2.1e6) &amp;&amp; (frequency &gt; 1e6)) {
    fs = 512;
  } else if (frequency &lt;= 1e6) {
    fs = 900;
  }

  // determine m, n, and d from frequency value
  m = floor(frequency*fs/25e6);
  n = ((frequency*fs/25e6) - m)*1000;
  clockgen.setupPLL(SI5351_PLL_A, m, n, 1000);
  clockgen.setupMultisynth(0, SI5351_PLL_A, fs, 0, 1);
}

Portable 40m Direct-conversion Transceiver Design

Having finished my master’s degree over a year ago now, I’ve started to see my thesis show up on various academic web sites. I decided I should probably link it on this site in the event that anyone is interested in building and/or designing their own QRP mono-band radio. Additionally, I’ve been doing some more experiments with QRP setups and like using this rig as a qualification vehicle. Being a mono-bander with a very narrow receive bandwidth, I just find it more sensitive to picking up weak signals, and it’s very easy to listen to when operating in less noisy environments. I’ve done a lot of comparisons with the KX3 (thanks to KK7B), and sometimes it’s just easier to copy signals closer to the noise floor on the DCT.

With that being said, any later posts that utilize this transceiver will point back here for reference. A full text PDF of the design is listed here:

https://www.researchgate.net/publication/346429865_Design_of_a_7-MHz_Portable_Direct_Conversion_Transceiver_with_Digitally_Controlled_Keying

73s DE K2NXF

7MHz Transmitter with AVR Soft-keyer

In my last post, I went over the design of a Colpitts crystal oscillator design that put out a moderately clean 7 MHz signal. In order to match the output impedance to 50 Ω, an NPN feedback pair (at least that’s what I’m calling it) was designed. While meeting the specs for driving an ADE-1 mixer, it consumed an unnecessary amount of current. I’ve been designing a tremolo effect recently (which I should be making on post on as well in the future) where I used a BS170 MOSFET to amplitude modulate an incoming audio signal. Without going into too much detail, I decided to adjust the DC bias point of an emitter-follower to sit at the threshold voltage of the BS170 to prevent from having to have a DC block immediately followed by another DC bias point. While looking at different transmitter designs on Homebrew RF Circuit Design Ideas, I came across a class C amplifier that used a similar technique to what I was doing in the tremolo effect combined with a Pierce oscillator. I did some experimentation and came up with the following circuit.

To be fair, this schematic is revision B, and it hasn’t been built yet. Revision A, however, is pretty much the same thing except R1 is omitted and the drain of Q3 connects to the source of Q6. The ATTiny85 takes the single-throw switch as the only input. When you turn it on, the switch acts like a standard on-off keyer for banging out morse code. If you hold the key down for 5 seconds, it changes to a beacon mode and starts tapping out my call sign. Holding the key down again changes operation into pulse mode with a frequency around 1 kHz. In pulse mode, you can actually pick up the signal on a standard AM receiver as seen in the demo video. The transmitter itself puts out 28 dBm running on a 12V supply and is around 82% efficient.

The PCB layout came out pretty quickly. It was the first time I have ever done double-sided PCB etching. Overall, I think it came out pretty well. There was small offset as you can see from the placement of the drill holes, but no harm, no foul. Performance was even a little better than the prototype. I mounted the board inside a Hammond 1590A enclosure and made a short demonstration video. It’s extremely simple, but I think it will be a useful piece of a larger project that I’m working on. It can also be easily adapted to a number of frequencies using a different crystal or loading Q2 to act as a frequency multiplier. I did experiment with this somewhat and was able to produce fairly strong second and third harmonics at the output. That’s about as far as I got though since I got distracted making theremin type sounds on my shortwave radio receiver.

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &amp;= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

#define CTL 0         // TX enable pin
#define SW  3        // input switch pin
#define N   10      // N periods in monopulse mode

bool            sw = LOW; 
bool            trig = LOW;               // monopulse trigger status
bool            tx = LOW;                // flag for TX enable
bool            keyer = LOW;            // flag for start of key press
int             mode = 0;              // modes: 0 =&gt; Key mode (default), 1 =&gt; ID mode, 2 =&gt; Monopulse
int             dit = 50;             // delay time for dit in ms
int             dash = 150;          // delay time for dash in ms
unsigned long   hold = 5e6;         // max hold value in microseconds
unsigned long   pause = 1e7;       // max pause between IDs in microseconds 
unsigned long   start = 0;        // start time in microseconds
unsigned long   last = 0;        // time of last ID;
long            c = 0;          // count variable for mode change



void setup() {
  pinMode(CTL,OUTPUT);
  pinMode(SW, INPUT);

  cli(); 
  /*--- TIMER1 CONFIG ---*/  
  TCCR1  = 0b01101000;
  GTCCR  = 0b00100000;
  
  TCCR0A = 0b00100000;
  TCCR0B = 0b00001011;    // last 3 bits set prescalar for Timer0

  cbi(TIFR,OCF1A);
  sbi(TIMSK,OCIE1A);
  OCR1A = 128;
  sei();

/* --- interrupt enable
    GIMSK = 0b00100000;     // turns on pin change interrupts
    PCMSK = 0b00001000;    // turn on interrupts on pins PB3
    sei(); 
*/
}

void loop() {  
  /*
    if (sw == LOW) { 
      start = micros();
      if (keyer == HIGH) {
       modeChk(); 
      } else {
       keyer = HIGH;
      }
    } else {
     keyer = LOW;
    }
  */
  // perform function based on mode
  switch(mode) {
    case 0:
      if (sw == LOW) {
        digitalWrite(CTL, HIGH); 
      } else {
        digitalWrite(CTL, LOW);
      }
    break;
    
    case 1:
      if (last == 0) {
        id();
      } else if ((micros() - last) &gt; pause) {
        id();
      }
    break;
    
    case 2:
      if (sw == LOW) {
        pulse();
      }
    break;
  }

  trig = LOW;                 // monopulse trigger reset  
}

ISR(TIMER1_COMPA_vect) {
    sw = digitalRead(SW);
    if (sw == LOW) { 
      if (keyer == HIGH) {
       modeChk(); 
      } else {
       start = micros();
       keyer = HIGH;
       trig = HIGH;
      }
    } else {
     keyer = LOW;
    }
} 

void modeChk() {
  if ((micros() - start) &gt; hold) {
     if (mode &lt; 2) {
       mode++;
     } else {
       mode = 0;
     }
     start = micros();
     stat();
     delay(1000);
  }
}

void pulse() {
  // pulse TX on and off N times
  for(int n = 0; n &lt; N; n++) {
      digitalWrite(CTL,HIGH);
      delayMicroseconds(500);
      digitalWrite(CTL,LOW);
      delayMicroseconds(500);
  }
}

void stat() {
  // tap out mode number in morse code
  for(int n = 0; n &lt; mode; n++) {
      digitalWrite(CTL,HIGH);
      delay(dit);
      digitalWrite(CTL,LOW);
      delay(dit);
  }

    for(int n = 0; n &lt; (5 - mode); n++) {
      digitalWrite(CTL,HIGH);
      delay(dash);
      digitalWrite(CTL,LOW);
      delay(dit);
  }
}

void id() {
  // tap out ID for K2NXF
  
// K
  digitalWrite(CTL,HIGH);
  delay(dash);
  digitalWrite(CTL,LOW);
  delay(dit);
  digitalWrite(CTL,HIGH);
  delay(dit);
  digitalWrite(CTL,LOW);
  delay(dit);
  digitalWrite(CTL,HIGH);
  delay(dash);

  digitalWrite(CTL,LOW);
  delay(dash);
  
// 2 
  digitalWrite(CTL,HIGH);
  delay(dit);
  digitalWrite(CTL,LOW);
  delay(dit);
  digitalWrite(CTL,HIGH);
  delay(dit);
  digitalWrite(CTL,LOW);
  delay(dit);
  digitalWrite(CTL,HIGH);
  delay(dash);
  digitalWrite(CTL,LOW);
  delay(dit);
  digitalWrite(CTL,HIGH);
  delay(dash);
  digitalWrite(CTL,LOW);
  delay(dit);
  digitalWrite(CTL,HIGH);
  delay(dash);
  digitalWrite(CTL,LOW);
  delay(dash);

// N
  digitalWrite(CTL,HIGH);
  delay(dash);
  digitalWrite(CTL,LOW);
  delay(dit);
  digitalWrite(CTL,HIGH);
  delay(dit);
  digitalWrite(CTL,LOW);
  delay(dash);

// X
  digitalWrite(CTL,HIGH);
  delay(dash);
  digitalWrite(CTL,LOW);
  delay(dit);
  digitalWrite(CTL,HIGH);
  delay(dit);
  digitalWrite(CTL,LOW);
  delay(dit);
  digitalWrite(CTL,HIGH);
  delay(dit);
  digitalWrite(CTL,LOW);
  delay(dit);
  digitalWrite(CTL,HIGH);
  delay(dash);
  digitalWrite(CTL,LOW);
  delay(dash);

// F
  digitalWrite(CTL,HIGH);
  delay(dit);
  digitalWrite(CTL,LOW);
  delay(dit);
  digitalWrite(CTL,HIGH);
  delay(dit);
  digitalWrite(CTL,LOW);
  delay(dit);
  digitalWrite(CTL,HIGH);
  delay(dash);
  digitalWrite(CTL,LOW);
  delay(dit);
  digitalWrite(CTL,HIGH);
  delay(dit);
  digitalWrite(CTL,LOW);
  delay(dash);

// new word
  digitalWrite(CTL,LOW);
  delay(dash);

last = micros();          // store current time at end of ID
  
} 

Testing a 40m Wire Dipole on Mt. Hood

Here’s a quick video showing how I deployed a DIY 40m dipole antenna on Mt. Hood a while back. Though not the most interesting video, I think it shows the results pretty well. I used an AirSpy HF+ supplied by KK7B for a project I’m working on. A Panasonic Toughbook CF-30 helped stave off the rain.

The dipole itself is two sections of 65 ft speaker wire connected to an SO-239 connector. I took a couple of sections of PVC and drilled holes in the end so I could attach some paracord and hoist it into the air. I tuned the antenna using a fancy MFJ-259C antenna tuner. Bethany and I both had very cold fingers after pulling the antenna down.

I’ll be posting more on this as it develops… for real though.

mobile APRS setup…

here’s a picture of my mobile APRS setup in the Jeep. it’s been a fun project from time to time. eventually, i plan on constructing a 2m vertical antenna to throw on the roof for better satellite reception while being mobile. i’m currently using a high-gain Comet mag-mount which is an excellent antenna. i was actually able to get out over 20 miles the other night on 146.52 with K5YLE using only 5 watts on the FT-60. for satellite reception, i’ll have to do a custom design.

the setup uses the FT-60R packet cable design into my Gentoo Linux laptop (Samsung N210) running soundmodem and Xastir.

Signals… from space?


none other than. yesterday, i attempted to record some very weak signals for the amateur satellite, ECHO AO-51, with my trusty Yaesu VX-3R. most of my attempts had been unsuccessful until i bought an additional Nagoya N-771 antenna to accompany the tiny 2m/70cm dual band radio. however, i have intercepted the signals from the low-earth orbiting satellite now on three separate occasions. the recording is definitely the weakest of the signals though due to a very low elevation in this particular pass and the interference created by the computer itself. hopefully, i’ll have a better one before long.

[audio:http://abrammorphew.com/notes/wp-content/uploads/2010/06/AO51-062910.mp3]