Errata: Schematic of 14 MHz handheld transceiver revised

OK, I admit it: Drawing schematics is not among my favourite hobbies. 😉 But it’s a must if you run an amateur radio blog. 😉 Sadly, I sometimes make mistakes. And I appreciate that attentive readers sometimes find the things I’ve lost sight of. Two minor changes thus had to be made in the schematic of my portable handheld rig for 14 MHz: REF pins at the AD9835 are now connected correctly to ground and the ATMega328 is now correctly power supplied. Thanks to Hellmuth, DF7VX, for his annotations!

Revised schematic of QRP SSB handheld transceiver for 14 MHz/20Meter by DK7IH (Peter Rachow)
Revised schematic of QRP SSB handheld transceiver for 14 MHz/20Meter by DK7IH (Peter Rachow)
Advertisements

The QRP-SSB transceiver goes “software defined”

One of the main advantages when you replace an LC-controlled VFO by a DDS-System is, aside from frequency stability, the possibility to control your VFO(s) by software. When designing my handheld QRP-rig I intenionally left the 2 ports for RS232-communication at the ATMega328 unused.

ATmega328 PIN layout DIL package
ATmega328 PIN layout DIL package

I always had in mind that I might one day setup a computer control so that the radio can be controlled by a PC. This is handy when you use the rig as a station transceiver in your home shack.

OK, let’s go to the whole story. First the hardware.

This is an easy chapter: You just have to connect the TxD and RxD pins of the microcontroller to a level changer like the well-known MAX232 chip by Maxim. This one converts the 0..5 volts level of the Atmega328 to +/- 12V of a PC’s RS232 interface. Browse the web for applications, you will find a lot. This is my one:

Interface for
Interface for “computer aided tuning” for QRP SSB transceiver

Power supply of the interface is powered by the RS232 connector. No external supply is needed. Instead of line 7 (RTS) you can also use line 4 (DTR). That’s the hardware that is required.

Software consists of two parts. First we want to look at the program modules realized in the QRP-radio.

Programming the ATmega328 for communication with a PC

All ATmegas have got functionality for universal asynchronous receiver transmitter (UART, sometimes called USART if synchronous communication functions are also implemented). This device is capable of working a large variety of baud rates for universal serial communication.

The INIT-Function

UART has to be initialized for transmitting, receiving, for appropriate comm parameters etc. The respective function looks like this and has to be called before the first data exchange can take place:

void uart_init(int uartval)
{
    UBRR0H = 0;
    UBRR0L = uartval; //Set baud rate by this value (25 stands for 19200!)
    
    // Activate TX, RX and RX interrupt
    UCSR0B |= (1<<TXEN0) | (1<<RXEN0) | (1<<RXCIE0);
    
    // set comm parameters
    // 8 databit, 1 stopbit, no parity
    UCSR0C |= (1<<UCSZ01) | (1<<UCSZ00);
    
    rx_buf_cnt = 0;
}

The parameter uartval must be calculated by a given formula and sets the baud rate depending on clock speed used by the MUC. Here is an overview for all baud rates and lots of clock speeds. For 8 MHz clock speed and 19200 baud I use the value of 25.

    //Init UART
    uart_init(25); //19200 Baud

Note that the output pin TxD does not have to be configured as an output. This is automatically done when you use this pin for communication. Best practise is to call it in main() function before entering the infinite loop.

Next we want to see two subroutines that deal with transmitting signals. First one is just designed to send a single character. The second routine uses this one to transmit complete strings.

void uart_putc(char tx_char)
{
    while(!(UCSR0A & (1<<UDRE0)));
    
    UDR0 = tx_char;
}

void uart_send_string(char *s)
{
    
    while(*s != 0)
    {
        uart_putc(*(s++));
        _delay_ms(5);
    }
    uart_putc(13);
    uart_putc(10);
}

Making the UART receive

UART of ATmega328 has one register that stores any received value: The UDR0-register. This one can be read by polling the register regularly or by entering an interrupt (signal) routine that is only executed when a signal is received. The last choice is the way I prefer. If you want to do this, be sure that global interrupt flag is enabled by stating

sei();

at the beginning of your main() function!

Here ist the routine that reads the UDR0-register when a character is received. Two global variables are declared first:

char rx_buf[32] ;

rx_buf_cnt;

The first variable stores the received byte(s), the second one is a counter that is increased each time a character is received.

SIGNAL(USART_RX_vect)
{
    unsigned char rx_char = UDR0;
    
    cli();
    
    if((rx_char == 10) || (rx_char == 13) || (rx_buf_cnt > RX_BUF_SIZE))
    {
        exec_command(rx_buf);
        clear_rx_buf();
        rx_buf_cnt = 0;
    }
    else
    {
        rx_buf[rx_buf_cnt++] = rx_char;
    }
    
    sei();
}

If there is a combination of CR and LF detected the received data will be checked what the user wants the software to do. Afterwards the receive buffer will be cleared. Two other functions are neccessary for this:

exec_command() uses the received characters to execute specific commands in the radio trasmitted by the user at the PC. clear_rx_buf() sets rx_buf[] variable to zero again and initializes the counter with 0.

// Set rx_buf to zero
void clear_rx_buf()
{
    int t1;
    
    for(t1 = 0; t1 < RX_BUF_SIZE; t1++)
    rx_buf[t1] = 0;
    rx_buf_cnt = 0;
}
//Execute the command that has been received by UART
//There are 2 basic set of commands
//
//a) (S)ET: Set a VFO or a frequency for example
//   Syntax: (S)ET(V)FO(X) = "SVX"
//           (S)ET(Q)RG(14234567)  = SQ14234567
//
//b) GET:
//   (G)ET(F)requency: Get current VFO-Frequency
//
void exec_command(char *com_str)
{
    unsigned long qrg = 0, fdec = 10000000;
    int t1;
    char *numstr = "            ";
    
    for(t1 = 0; t1 < 12; t1++)
    {
        *(numstr + t1) = 0;
    }
    
    
    if(com_str[0] == 'S') //Set-command detected
    {
        switch(com_str[1]) //Search sub command
        {
            //SETVFO-command detected
            case('V'): if((com_str[2] >= 'A') && (com_str[2] <= 'M'))
            {
                vfo_cnt = com_str[2] -65;
                set_frequency(vfo[vfo_cnt],interfreq, AD9835UPDATE);
                show_vfo(vfo_cnt) ;
            }
            break;
            
            //SETQRG-command detected
            case('Q'):  for(t1 = 2; t1 < 9; t1++)
            {
                qrg += (com_str[t1] - 48) * fdec;
                fdec /= 10;
            }
            vfo[vfo_cnt] = qrg;
            set_frequency(qrg,interfreq, AD9835UPDATE);
            break;
            
        }
    }
    
    if(com_str[0] == 'G') //GET-command detected
    {
        switch(com_str[1]) //Search sub command
        {
            //GET frequency-command detected
            case('F'):  lng2str(vfo[vfo_cnt], numstr);
            uart_send_string(numstr);
            break;
        }
    }
    
}

How this function works:

Commands are combinations of two letters and maybe a parameter. If the first letter is an “S” a SET-command is performed. Something in the transceiver will be changed. If there is a “G”, a reading operation has been received and the requested data will be sent to the PC.

Examples: “SQ14256000” stands for “Set QRG 14.256 MHz”. “SVA” switches to internal VFOA of the radio (“Set VFO A“). “GF” is a “Get Frequency”-command and sends the frequency of the current VFO to the PC as a string. Therefore the function lng2str() has been created. This function manipulates a char-pointer given as a parameter to convert a numeric value to a *char pointer. Notice that therefore there is no return value. String data will be written to the given pointer adress.

//Convert a number to a pointer
void lng2str(long num, char *s)
{
    
    long t1, t2, n = num, r, x = 1;
    int digits;
    
    
    /* Calc digit number
    for(t1 = 1; t1 < 10 && (n / x); t1++)
    {
        x *= 10;
    }
    digits = t1 - 1;
    
    
    if(!digits)
    {
        digits = 1;
    }
    
    for(t1 = digits - 1; t1 >= 0; t1--)
    {
        x = 1;
        for(t2 = 0; t2 < t1; t2++)
        {
            x *= 10;
        }
        r = n / x;
        *s++ = r + 48;
        
        n -= r * x;
    }
    
    
}

These were the things neccessary in the radio’s software to ensure communication with a PC host. Now we want to examine the PC software.

The PC software for the computer aided tuning

The code is written in the very old version Visual Basic 5. VB is still functionable, by the way 😉 . And it is not very complicated. All that you have to do is to pass the codes for the desired functions to the radio via the serial port. For first tests I used a simple terminal program and typed the codes by hand.

The more comfortable things with my software are functions that make tuning, changing frequencies and above all DX-clustering, possible by just a click. The user interface is pretty simple:

Computer aided tuning for QRP SSB transceiver and PC (written in VB5)
Computer aided tuning for QRP SSB transceiver and PC (written in VB5)

A complete package in ZIP-file format can be downloadad from here. Please notice that the file is named qrpcat.zi_ due to the fact that ZIP extension are not allowed on the server. So please rename the file as qrpcat.zip before processing it!

DX-cluster integration

I have to admit that I’m not the big enthusiast for clusters. I prefer to listen to the band. But it was interesting to achieve a direct transfer from a cluster reported frequency directly into the transceiver. Besides I learned a lot about the principles of these systems the last two days.

The basics first. In brief: DX-clusters form a worldwide net of servers exchanging data. This means that one DX station that is put into one cluster server is transferred immediately to the others. The servers use the TELNET protocol which is a simple text-only communication protocol. Therefore cluster messages are in text form.

In my Visual Basic application I use the WINSOCK-control to establish a communication between my computer and the cluster server.

QRP CAT Winsock control
QRP CAT Winsock control

WINSOCK is a universal control available in Microsoft Windows software (for VB and C++ as far as I know). The routines presented here are very simple and not 100% fool-proof. They are more for learning, even if they work well.

First thing you have do is to connect to a DX cluster. This requires a server name only. No password is neccessary for the one given in the server statement:

Private Sub cmdTNConnect_Click()
   If 
     wskTN.State = sckClosed Then
     wskTN.RemotePort = 23
     wskTN.RemoteHost = "n7od.pentux.net"
     wskTN.Connect
   End If
End Sub

It’s very simple and merely to show you how it works. There is no error checking because I don’t want to confuse you. Use a push button to activate the code!

Normally the cluster responds with a “welcome” message to this opening . By the end of the message you must enter your callsign. Use this function to transfer the requested data:

Private Sub cmdSendTN_Click()

    If wskTN.State = sckConnected Then
        wskTN.SendData txtSendTN & vbCrLf
    End If
    
End Sub

txtSendTN is the text field the data is read from. Put your call in there! sckConnected is defined in a library and states that you are connected to the server.

So, this is all that had to be done to connect you to the server and to send some basic data. Now you just have to lean back and read the DX data that is transferred from the cluster host. To get this into a string variable, you have to use the DataArrival-method of the WINSOCK control. Some globals have to be declared first:

Global strTNRcvd As String
[...]
Private Sub wskTN_DataArrival(ByVal bytesTotal As Long)
  
  Dim strData As String
  Dim strResult As String
  
  wskTN.GetData strData
  
  strTNRcvd = strTNRcvd & strData
  If InStr(1, strData, vbCrLf) > 0 Then
      lstDX.Additem strTNRcvd
      strTNRcvd = ""
  End If
  
End Sub

Instead of lstDX.Additem strTNRcvd just do something useful with the received information. In my software I filter the frequency (only stations on the 20 meter band are interesting for my radio), extract the callsign and put this combination into the list of favourites. Just load down the ZIP file and see on your own.

So, that is all so far about the project “The QRP transceiver goes software defined”. If you have any questions, don’t hesitate to mail me: peter.rachow(at)web.de.

73 and thanks for reading!

Peter

Update: Software for DDS-controlled QRP SSB handheld transceiver (AD9835, ATmega328P)

Recently I had a lot of new ideas for my QRP SSB handheld radio. When starting to program the first changes to my old software, I found out that the ATmega8 had become too small concerning flash size. The program became too large for 8kb memory size. The only thing I could do was to use another MCU that was PIN-compatible and did not require lots of code changes.

ATMEL has got the ATmega48, ATmega88, ATmega168, ATmega328(P)-series on the market. These are also, like the ATmega8, controllers packed in 28-pin-DIL-package. Above all, they are PIN-compatibel to the ATmega8. I decided to use an ATmega328 to update my radio. This MCU provides 32kB of FLASH memory which is more than enough for my application.

Some slight code alternations also had to be done, because some register names have been changed and the SLEEP-method had to be slightly revised. What I deeply regretted was the fact that I couldn’t use my old YAAP-ISP software anymore because it’s much too old and does not support this controller. But AVRDUDE works fine. These were the main obstacles that I had to deal with. After 2 or 3 hours of programming the software job was done. The ATmega328P does a perfect job in my transceiver. Full code will be presented by the end of this article.

Important: The ATmega328P reaches the customer with the factory setting of an 8MHz rate for its main clock. But there is a fuse that divides this clock rate by the factor of 8. This one is activated by factory also. So, your MCU performs like an old ATmega8 with factory default set if this fuse is not changed. Goto this website and find out the correct fuse settings for you MCU. AVRDUDE needs this command line to achieve the correct settings:

-U lfuse:w:0xe2:m -U hfuse:w:0xd9:m -U efuse:w:0xff:m

If you want to use my software for your own project, I will give you a brief description of how the software works and what features it has got (so far). So, first, here’s the completely revised schematic of the transceiver. You are particulary recommended to have a closer look at the AD9835/ATmega328 VFO unit!

SSB QRP handheld tranceiver for 14MHz/20mtr. Rev. 5 (C) Peter Rachow (DK7IH)
SSB QRP handheld tranceiver for 14MHz/20mtr. Rev. 5 (C) Peter Rachow (DK7IH)

Major modifcations: I now use a debouncing circuit for the 5 switches that the operator uses to control the radio. Some MCU ports have been changed compared with older versions of the radio. Some switches have got multiple functions now depending on which part of the software/menu you are currently using.

Watch this picture:

Controls used for SSB QRP handheld transceiver
Controls used for SSB QRP handheld transceiver

The controls are:

VFO: You can now select from 12 VFOs. Pressing this button switches to the next VFO in rising order. Once you’ve reached the last VFO the run starts with VFOA again.

FUNC: Leads you to a set of submenus where you can

  • store a frequency to the current VFO
  • SCAN the band starting from the current frequency either up or down (tuning step ist the last step that you used when tuning)
  • SCAN the VFOs with possibiliy to skip any VFO during the scan isperformed
  • Put the MCU into SLEEP-mode to reduce noise to a maximum

STEP: Sets the tuning step (1, 5, 10, 50, 100, 500, 1000, 5000 Hz). 10Hz tuning step is restored 1 second after the last tuning is finished.

Tune UP/DOWN lets you alter the frequency according to current tuning step.

Let’s go into the FUNC menu. After pressing this button you are asked if you want to store the current frequency to the current VFO:

Saving a frequency into a VFO storage place

“STOR A?” means that you can save the frequency displayed to VFOA. Just press the “STEP (Y)” button. If you don’t want to to do this, press “FUNC (N)” which is read as “NO”.

Scanning the band

Next question is “SCN QRG?”. “SCN” abbrieviates “SCAN”. If you press now either the UP or DOWN button the scan starts. Tuning step applied is the last step that you’ve set before you entered this function. The reset that is automatically performed to 10Hz tuning shift will not affect the tuning step since it has not been manually set. If you don’t want to scan, just press “FUNC (N)”.

Scanning the VFOs

Afterwards you can go to a scanning procedure thru the VFOs. Answering “SCN VFO?” with “yes” will scan each VFO and leave the receiving frequency set for 4 seconds until the next VFO is switched. If you want a certain VFO to be skipped (e. g. if there is noise or an unwanted station), just press the”VFO”-button while the VFO is active. In the next round this VFO will be ignored.

Working SPLIT-Mode

The software allows you to choose different frequencies for transmit and receive. In other words: You can work split with this radio.

The receiving frequency alway is that of the VFO currently used. The transmit frequency can be selected with this function.

“Split?” must be answered with the “STEP (Y)”-button  if you wish to activate this option. In the next step you must select a VFO that you want to use as transmit frequency. Use UP or DOWN-key to alter the selected VFO. Confirm your choice with the “STEP (Y)”-button. If you wish to abort, press “FUNC (N)”.

If you want to switch off SPLIT-mode enter the FUNC-Menu again. Select “Split” and then, when asked to select the TX VFO, press “FUNC (N)”.

Ultimate noise reduction: Putting the MCU to sleep

Last question is if you want to put the MCU to SLEEPMODE. Even if the MCU does not produce any disturbing noise (at least in my rig) it can be uesful to switch the off completely. Answer the respective question with”STEP (Y)” the MCU is now switched off. Pressing either UP or DOWN tuning button will wake up the MCU again.

So, here’s the latest version of my code. See you soon and thanks for reading. 73 de Peter (DK7IH)

/*****************************************************************/
/*     DDS for QRP SSB Transceiver w. ATMega328 und AD9835       */
/*  ************************************************************ */
/*  MUC:              ATMEL AVR ATmega328, 8 MHz                 */
/*                                                               */
/*  Compiler:         GCC (GNU AVR C-Compiler)                   */
/*  Author:           Peter Rachow (DK7IH)                       */
/*  Last change:      17 AUG 2015                                */
/*****************************************************************/
/* PORT usage */
//
//OUTPUT
//
// LCD
// RS      = PB6
// E       = PB7
// D4...D7 = PD4..PD7
//
// SPI to AD9835
// FSYNC    = PB0
// SDATA:   = PB1
// SCLK:    = PB2
//
//INPUTS
//
//Vcc-Monitor:    PINC0 (analogue input)
//TX-line monitor PINC2 (detects when radio is on air)
//VFO-Select:     PINC3 push button
//FUNC:           PINC4 push button
//TUN STEP:       PINC5 push button
//TUNE UP:        PIND2 push button
//TUNE DOWN:      PIND3 push button
//
//
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/eeprom.h>
#include <util/delay.h>
#define F_CPU 8000000
#define MAXSHIFT 7
#define MAXVFO 12
//AD9835 Modes
#define AD9835INIT 1 //AD9835 will be fully initialized with frequency value
// (sets AD9835 into sleep mode for some ms)
#define AD9835UPDATE 0 //AD9835 frequency will be updated because frequency
//has been changed by user
//Timer 1
unsigned long runsecs = 0;
int main(void);
/*******************/
//       SPI
/*******************/
//Port usage
//FSYNC: PB0 (1)
//SCLK: PB1 (2)
//SDATA: PB2 (4)
void spi_start(void);
void spi_send_bit(int);
void spi_send_byte(unsigned int);
void spi_send_word(unsigned int);
void spi_stop(void);
void set_frequency(unsigned long, unsigned long, int);
char *freq_shift_str[MAXSHIFT + 1] = {"  1", "  5", " 10", " 50", "100", "500", " 1k", " 5k"};
/***************/
/* LCD-Display */
/***************/
//Data: PD4..PD7
//E: PC0
//RS: PC1
#define LCD_INST 0x00
#define LCD_DATA 0x01
void lcd_write(char, unsigned char);
void set_rs(char);
void set_e(char);
void lcd_init(void);
void lcd_cls(void);
void lcd_putchar(int, int, unsigned char);
void lcd_putstring(int, int, char*);
int lcd_putnumber(int, int, long, int, int, char, char);
void lcd_display_test(void);
void show_freq(long);
void show_step(int);
void show_vfo(int);
//****************
//      ADC
//****************
#define ADWAITSTATE 3
int get_adc(int);
//EEPROM
void savefreq(int, unsigned long);
unsigned long loadfreq(int vfo_num);
//SLEEPMODE
unsigned long idlesecs = 0;
void save_cur_vfo(int);
int load_cur_vfo(void);
void save_cur_vfo(int vfo_num)
{
    while(!eeprom_is_ready());
    eeprom_write_byte((uint8_t*)511, vfo_num);
}
int load_cur_vfo(void)
{
    int lvfo = eeprom_read_byte((uint8_t*)511);
    if(lvfo < 0 || lvfo > MAXVFO)
    {
        return 0;
    }
    else
    {
        return lvfo;
    }
}
void savefreq(int vfo_num, unsigned long num)
{
    unsigned int hiword, loword;
    unsigned char hmsb, lmsb, hlsb, llsb;
    int start_adr = vfo_num * 4;
    cli();
    hiword = num / 65536;
    loword = num - hiword * 65536;
    hmsb = hiword / 256;
    hlsb = hiword - hmsb * 256;
    lmsb = loword / 256;
    llsb = loword - lmsb * 256;
    while(!eeprom_is_ready());
    eeprom_write_byte((uint8_t*)start_adr, hmsb);
    while(!eeprom_is_ready());
    eeprom_write_byte((uint8_t*)start_adr + 1, hlsb);
    while(!eeprom_is_ready());
    eeprom_write_byte((uint8_t*)start_adr + 2, lmsb);
    while(!eeprom_is_ready());
    eeprom_write_byte((uint8_t*)start_adr + 3, llsb);
    sei();
}
unsigned long loadfreq(int vfo_num)
{
    unsigned long num2;
    unsigned char hmsb, lmsb, hlsb, llsb;
    int start_adr = vfo_num * 4;
    cli();
    hmsb = eeprom_read_byte((uint8_t*)start_adr);
    hlsb = eeprom_read_byte((uint8_t*)start_adr + 1);
    lmsb = eeprom_read_byte((uint8_t*)start_adr + 2);
    llsb = eeprom_read_byte((uint8_t*)start_adr + 3);
    num2 = (unsigned long) 16777216 * hmsb + 65536 * hlsb + (unsigned int) 256 * lmsb + llsb;
    if(num2 >= 13900000 && num2 <= 14380000)
    {
        return num2;
    }
    else
    {
        return 1415000;
    }
    sei();
}
//************
//    SPI
//************
void spi_start(void)
{
    //FSYNC lo
    PORTB &= ~(1); // Bit PB0 = 0
}
void spi_stop(void)
{
    //FSYNC hi
    PORTB |= 1; // Bit PB0 = 1
}
void spi_send_bit(int sbit)
{
    //Bit set or erase
    if(sbit)
    {
        PORTB |= 2;  //SDATA Bit PB1 set
    }
    else
    {
        PORTB &= ~(2);  //SDATA Bit PB1 erase
    }
    //SCLK hi
    PORTB |= 4;  //Bit PB2 set
    //SCLK lo
    PORTB &= ~(4);  //Bit PB2 erase
}
void spi_send_byte(unsigned int sbyte)
{
    int t1, x = 128;
    for(t1 = 0; t1 < 8; t1++)
    {
        spi_send_bit(sbyte & x);
        x = x >> 1;
    }
    PORTB |= 2;  //PB1 SDATA hi
}
void spi_send_word(unsigned int sbyte)
{
    unsigned int t1, x = 32768;
    for(t1 = 0; t1 < 16; t1++)
    {
        spi_send_bit(sbyte & x);
        x = x >> 1;
    }
    PORTB |= 2; //PB1 SDATA hi
}
/**************************************/
/* LCD routines                       */
/**************************************/
//Port usage at MUC:
//LCD-Data: PD0..PD4
//E: PC0
//RS: PC1
/* Send one byte to LCD */
void lcd_write(char lcdmode, unsigned char value)
{
    int x = 16, t1;
    set_rs(lcdmode);    // RS=0 => command, RS=1 => character
    _delay_ms(3);
    set_e(1);
    /* Hi nibble */
    for(t1 = 0; t1 < 4; t1++)
    {
        if(value & x)
        {
            PORTD |= x;              // Set Bit
        }
        else
        {
            PORTD &= ~(x);          // Erase Bit
        }
        x *= 2;
    }
    set_e(0);
    x = 16;
    set_e(1);
    /* Lo nibble */
    for(t1 = 0; t1 < 4; t1++)
    {
        if((value & 0x0F) * 16 & x)
        {
            PORTD |= x;              // Set bit
        }
        else
        {
            PORTD &= ~(x);          // erase bit
        }
        x *= 2;
    }
    set_e(0);
}
/* RS set */
void set_rs(char status) /* PORT PB6  */
{
    if(status)
    {
        PORTB |= 64;
    }
    else
    {
        PORTB &= ~(64);
    }
}
/* E set */
void set_e(char status)  /* PORT PB7*/
{
    if(status)
    {
        PORTB |= 128;
    }
    else
    {
        PORTB &= ~(128);
    }
}
//Send 1 char to LCD
void lcd_putchar(int row, int col, unsigned char ch)
{
    lcd_write(LCD_INST, col + 128 + row * 0x40);
    lcd_write(LCD_DATA, ch);
}
//Send string to LCD
void lcd_putstring(int row, int col, char *s)
{
    unsigned char t1;
    for(t1 = col; *(s); t1++)
    {
        lcd_putchar(row, t1, *(s++));
    }
}
//Clear LCD
void lcd_cls(void)
{
    lcd_write(LCD_INST, 1);
}
/* LCD-Display init */
void lcd_init(void)
{
    // Basic settings: 2 lines, 5x7 matrix, 4 bit data bus
    lcd_write(LCD_INST, 40);
    // Display on, Cursor off, Blink off
    lcd_write(LCD_INST, 12);
    // Entrymode !cursoincrease + !displayshifted
    lcd_write(LCD_INST, 4);
    lcd_cls();
}
//Write number with given amount on digits to LCD
//set decimal where needed (-1 if not needed)
int lcd_putnumber(int row, int col, long num, int digits, int dec, char orientation, char showplussign)
{
    char cl = col, minusflag = 0;
    unsigned char cdigit[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, digitcnt = 0;
    long t1, t2, n = num, r, x = 1;
    if(num < 0)
    {
        minusflag = 1;
        n *= -1;
    }
    if(digits == -1)
    {
        for(t1 = 1; t1 < 10 && (n / x); t1++)
        {
            x *= 10;
        }
        digits = t1 - 1;
    }
    if(!digits)
    {
        digits = 1;
    }
    for(t1 = digits - 1; t1 >= 0; t1--)
    {
        x = 1;
        for(t2 = 0; t2 < t1; t2++)
        {
            x *= 10;
        }
        r = n / x;
        cdigit[digitcnt++] = r + 48;
        if(t1 == dec)
        {
            cdigit[digitcnt++] = 46;
        }
        n -= r * x;
    }
    digitcnt--;
    t1 = 0;
    /* Output of number to be displayed on LCD */
    switch(orientation)
    {
        case 'l':  //orientiation left
        cl = col;
        if(minusflag)
        {
            lcd_putchar(row, cl++, '-');
            digitcnt++;
        }
        else
        {
            if(showplussign)
            {
                lcd_putchar(row, cl++, '+');
                digitcnt++;
            }
        }
        while(cl <= col + digitcnt)
        lcd_putchar(row, cl++, cdigit[t1++]);
        break;
        case 'r':  //orientiation right
        t1 = digitcnt;
        for(cl = col; t1 >= 0; cl--)
        {
            lcd_putchar(row, cl, cdigit[t1--]);
        }
        if(minusflag)
        {
            lcd_putchar(row, --cl, '-');
        }
    }
    if(dec == -1)
    {
        return digits;
    }
    else
    {
        return digits + 1;
    }
}
//Display frequency line 0 left
void show_freq(long fr)
{
    lcd_putnumber(0, 0, fr / 10, -1, 5, 'l', 0);
}
//Display tuning step line 1 right
void show_step(int st)
{
    lcd_putstring(1, 5, freq_shift_str[st]);
}
//Display VFO info, line 1 left
void show_vfo(int vfo_num)
{
    lcd_putstring(1, 0, "VFO");
    lcd_putchar(1, 3, vfo_num + 65);
}
//Set AD9835 to desired frequency
//2 Modes:
//Full initialization
//or
//simple readadjustment of freq (to be preferred beause of avoiding
//
//Full init sets the AD9835 to short sleep thus generating a short interruption
//in receiving/transmitting
void set_frequency(unsigned long freq, unsigned long ifrequency, int ad9835fullinit)
{
    unsigned long fxtal = 50000450;  //fCrystal in MHz
    double fword0;
    unsigned long fword1;
    unsigned long hiword, loword;
    unsigned char hmsb, lmsb, hlsb, llsb;
    fword0 = (double) (freq - ifrequency) / fxtal;
    fword1 = (unsigned long) (fword0 * 0xFFFFFFFF);
    //Split 32 Bit in 2 * 16 Bit
    hiword = (unsigned long) fword1 / 65536;
    loword = (unsigned long) fword1 - hiword * 65536;
    //Slipt 1st 16 Bit to 2 * 8 Bit
    hmsb = hiword / 256;
    lmsb = hiword - hmsb * 256;
    //Split 2nd 16 Bit to 2 * 8 Bit
    hlsb = loword / 256;
    llsb = loword - hlsb * 256;
    if(ad9835fullinit)
    {
        //init, set AD9835 to sleepmode
        spi_start();
        spi_send_word(0xF800);
        spi_stop();
    }
    //Send frequency (double) word to DDS
    spi_start();
    spi_send_word(0x33 * 0x100 + hmsb);
    spi_stop();
    spi_start();
    spi_send_word(0x22 * 0x100 + lmsb);
    spi_stop();
    spi_start();
    spi_send_word(0x31 * 0x100 + hlsb);
    spi_stop();
    spi_start();
    spi_send_word(0x20 * 0x100 + llsb);
    spi_stop();
    //End of sequence
    spi_start();
    if(ad9835fullinit)
    {
        //AD9835 wake up from sleep
        spi_send_word(0xC000);
    }
    else
    {
        //AD9835 freq data update, no full init
        spi_send_word(0x8000);
    }
    spi_stop();
    //Display frequency
    show_freq(freq);
}
//Interrupt routines
ISR(TIMER1_OVF_vect)          // Timer1 overflow
{
    runsecs++;
    TCNT1 = 57724;
}
ISR(INT0_vect) //INT0 for ATmega being woke up from sleep, can be left
{               //empty but must exist!
}
ISR(INT1_vect)
{
}
//***************************************************
//                      ADC
//***************************************************
//Read a value from ADC
int get_adc(int adcmode)
{
    int adc_val = 0;
    ADMUX = (ADMUX &~(0x1F)) | (adcmode & 0x1F);     // Activate channel "adcmode"
    _delay_ms(ADWAITSTATE);
    ADCSRA |= (1<<ADSC);
    _delay_ms(ADWAITSTATE);
    adc_val = ADCL;
    adc_val += ADCH * 256;
    while(ADCSRA & (1<<ADSC));
    return adc_val;
}
int main()
{
    unsigned long freq1, freq2;  //Frequency in Hz to be generated
    unsigned long vfo[MAXVFO + 1] = {}; //12 VFO-frequencies
    char skip_vfo[MAXVFO + 1];
    unsigned int  cur_freq_shift = 2; //Tuning step in Hz
    unsigned int freq_shift[MAXSHIFT + 1] = {1, 5, 10, 50, 100, 500, 1000, 5000}; //Possible tuning steps for UP/DOWN-keys
    int scan_mode = 0;
    int vfo_cnt = 0;
    unsigned long interfreq = 9832000; //Interfrequency in Hz (depends on filter used)
    unsigned int adc_val;
    unsigned long runsecsold1 = 0;
    unsigned long runsecsold2 = 0;  //Timer control for switching display between VFO and VOLTAGE
    unsigned long runsecsold3 = 0;  //Timer control for resseting tuning step to 10 secs after idel time
    char displayed = 0;
    unsigned long voltage2;
    int t1;
    //Variables for FUNC-Menu
    int exit_func = 0;
    int exit_loop = 0;
    //Tuning step for frequency scan
    int scan_step = 2;
    //Variables for SPLIT-MODE
    int vfo_tx = 0;
    int split_mode = 0;
    int tx_freq_set = 0;
    /* Set ports */
    /* OUTPUT */
    DDRB = 0xC7; //SPI (PB0..PB2) LCD RS and E on PB6 and PB7
    DDRD = 0xF0; //LCD (Data) on PD4...PD7
    /*Input*/
    PORTD = 0x0F; //Pull-Up resistors on
    //PD2 = Tune up
    //PD3 = Tune down
    PORTC = 0x3E; ////Pull-Up resistors on for inputs
    //TX INDICATOR:   PINC2
    //VFO-Select:     PINC3
    //FUNC:           PINC4
    //TUN STEP:       PINC5
    //TUNE UP:        PIND2
    //TUNE DOWN:      PIND3
    //Start LCD
    lcd_init();
    _delay_ms(50);
    //Watchdog off
    WDTCSR = 0;
    WDTCSR = 0;
    //ADC init
    ADMUX = (1<<REFS0);     // Reference = AVCC
    ADCSRA = (1<<ADPS2) | (1<<ADPS1) | (1<<ADEN); //Prescaler 64 and ADC activated
    ADCSRA |= (1<<ADSC);
    while (ADCSRA & (1<<ADSC));
    adc_val = ADCL; //Read one sample vaue and discard
    adc_val += ADCH * 256;
    adc_val = 0;
    //Timer 1
    TCCR1A = 0;                         // normal mode, no PWM
    TCCR1B = (1<<CS12) + (1<<CS10) ;   // start Timer with system clock Prescaler = /1024
    //Trigger des Overflow each sec.
    TIMSK1 = (1<<TOIE1);               // overflow activated.
    TCNT1 = 57724;                     //init value for measuering one second
    //Load VFO frequencies from EEPROM if available
    freq2 = 14180000;
    for(t1 = 0; t1 <= MAXVFO; t1++)
    {
        freq1 = loadfreq(t1);
        if(freq1 > 13000000)
        {
            vfo[t1] = freq1;
        }
        else
        {
            vfo[t1] = freq2;
            freq2 += 10000;
        }
    }
    //Get last VFO used
    vfo_cnt = load_cur_vfo();
    freq1 = vfo[vfo_cnt];
    set_frequency(freq1, interfreq, AD9835INIT);
    lcd_putstring(0, 0, "QRP SSB");
    lcd_putstring(1, 0, "TRX V2.2");
    _delay_ms(600);
    lcd_cls();
    show_freq(freq1);
    show_step(cur_freq_shift);
    show_vfo(vfo_cnt);
    runsecsold2 = runsecs;
    idlesecs = runsecs;
    sei();
    for(;;)
    {
        //KEYS:
        //TX INDICATOR:   PINC2
        //VFO-Select:     PINC3
        //FUNC:           PINC4
        //TUN STEP:       PINC5
        //TUNE UP:        PIND2
        //TUNE DOWN:      PIND3
        //VFO-Select
        if(!(PINC & (1<<PINC3)))
        {
            if(vfo_cnt < MAXVFO)
            {
                vfo_cnt++;
            }
            else
            {
                vfo_cnt = 0;
            }
            //set frequency
            set_frequency(vfo[vfo_cnt], interfreq, AD9835UPDATE);
            lcd_cls();
            freq1 = vfo[vfo_cnt];
            save_cur_vfo(vfo_cnt);
            set_frequency(freq1, interfreq, AD9835UPDATE);
            show_vfo(vfo_cnt);
            //show stepanzeigen
            lcd_putstring(1, 5, "  ");
            show_step(cur_freq_shift);
            idlesecs = runsecs;
            //wait for key release
            while(!(PINC & (1<<PINC3)));
        }
        if(!(PIND & (1<<PIND2))) //TUNE UP
        {
            freq1 += freq_shift[cur_freq_shift];
            set_frequency(freq1, interfreq, AD9835UPDATE);
            runsecsold3 = runsecs;
            idlesecs = runsecs;
            if(scan_mode)
            {
                scan_mode = 0;
                show_step(cur_freq_shift);
            }
        }
        if(!(PIND & (1<<PIND3))) //TUNE DOWN
        {
            freq1 -= freq_shift[cur_freq_shift];
            set_frequency(freq1, interfreq, AD9835UPDATE);
            runsecsold3 = runsecs;
            idlesecs = runsecs;
            if(scan_mode)
            {
                scan_mode = 0;
                show_step(cur_freq_shift);
            }
        }
        //FUNC
        if(!(PINC & (1<<PINC4)))
        {
            scan_mode = 0;
            exit_func = 0;
            while(!(PINC & (1<<PINC4)));
            //STORE FREQ TO VFO
            lcd_cls();
            lcd_putstring(1, 0, "STOR");
            lcd_putchar(1, 5, vfo_cnt + 65);
            lcd_putchar(1, 6, '?');
            show_freq(freq1);
            while((PINC & (1<<PINC5)) && (PINC & (1<<PINC4)));
            if(!(PINC & (1<<PINC5)))
            {
                while(!(PINC & (1<<PINC5)));
                lcd_cls();
                vfo[vfo_cnt] = freq1;
                show_freq(freq1);
                lcd_putstring(1, 0, "Saved");
                lcd_putchar(1, 6, vfo_cnt + 65);
                lcd_putchar(1, 7, '.');
                savefreq(vfo_cnt, freq1);
                _delay_ms(1000);
                lcd_putstring(1, 5, "  ");
                exit_func = 1;
            }
            else
            {
                while(!(PINC & (1<<PINC4)));
            }
            //Scan frequency, trigger with UP/DOWN key, cancel with FUNC-Key
            if(!exit_func)
            {
                lcd_cls();
                lcd_putstring(0, 0, "SCN QRG?");
                lcd_putstring(1, 0, "DOWN UP");
                //Wait for key
                //UP, DOWN, FUNC
                while((PIND & (1<<PIND2)) && (PIND & (1<<PIND3)) && (PINC & (1<<PINC4)));
                //UP
                if(!(PIND & (1<<PIND2)))
                {
                    scan_mode = 1;
                    exit_func = 1;
                    while(!(PIND & (1<<PIND2)));
                }
                //DOWN
                if(!(PIND & (1<<PIND3)))
                {
                    while(!(PIND & (1<<PIND3)));
                    scan_mode = 2;
                    exit_func = 1;
                }
                //Cancel
                if(!(PINC & (1<<PINC4)))
                {
                    while(!(PINC & (1<<PINC4)));
                    lcd_cls();
                    show_freq(freq1);
                    show_step(cur_freq_shift);
                }
            }
            if(!exit_func)
            {
                lcd_cls();
                lcd_putstring(0, 0, "SCN VFO?");
                lcd_putstring(1, 0, "  NO YES");
                //Wait for key press Y/N
                //
                while((PINC & (1<<PINC5)) && (PINC & (1<<PINC4)));
                //Scan VFOs
                if(!(PINC & (1<<PINC5)))
                {
                    while(!(PINC & (1<<PINC5)));
                    //Set all skip flags to 0
                    for(t1 = 0; t1 <= MAXVFO; t1++)
                    {
                        skip_vfo[t1] = 0;
                    }
                    lcd_cls();
                    lcd_putstring(0, 0, "SCANNING");
                    for(t1 = 0; t1 < 8; t1++)
                    {
                        lcd_putchar(1, t1, '.');
                        _delay_ms(100);
                    }
                    exit_loop = 0;
                    while(!exit_loop)
                    {
                        lcd_cls();
                        for(t1 = 0; t1 <= MAXVFO && !exit_loop; t1++)
                        {
                            if(!skip_vfo[t1]) //Scan only if VFO not in skip list
                            {
                                runsecsold1 = runsecs;
                                lcd_cls();
                                set_frequency(vfo[t1], interfreq, AD9835UPDATE);
                                show_vfo(t1);
                                //Wait 4 seconds
                                while((PINC & (1<<PINC5)) && (PINC & (1<<PINC4)) && runsecs < runsecsold1 + 4)
                                {
                                    lcd_putchar(1, 4 + runsecs - runsecsold1, '.');
                                    if(!(PINC & (1<<PINC3))) //Skip current VFO
                                    {
                                        while(!PINC & (1<<PINC3));
                                        skip_vfo[t1] = 1;
                                        lcd_putstring(1, 0, "SKIPPED!");
                                        _delay_ms(500);
                                        runsecsold1 -= 4;
                                    }
                                }
                                if(!(PINC & (1<<PINC5))) //Select VFO
                                {
                                    while(!(PINC & (1<<PINC5)));
                                    exit_loop = 1;
                                    vfo_cnt = t1;
                                    freq1 = vfo[vfo_cnt];
                                    set_frequency(freq1, interfreq, AD9835UPDATE);
                                }
                                if(!(PINC & (1<<PINC4))) //Cancel
                                {
                                    while(!(PINC & (1<<PINC4)));
                                    exit_loop = 1;
                                }
                            }
                        }
                    }
                    exit_func = 1;
                }
            }
            if(!exit_func)
            {
                while(!(PINC & (1<<PINC4)));
                if(split_mode)
                {
                    split_mode = 0;
                }
                lcd_cls();
                lcd_putstring(0, 0, "Split?");
                lcd_putstring(1, 0, "  NO YES");
                //Wait for key press Y/N
                //
                while((PINC & (1<<PINC5)) && (PINC & (1<<PINC4)));
                //Select VFO for transmit (VFO for receive will be the one currently in use)
                if(!(PINC & (1<<PINC5)))
                {
                    while(!(PINC & (1<<PINC5)));
                    lcd_cls();
                    lcd_putstring(0, 0, "SEL TX");
                    lcd_putstring(1, 0, "VFO");
                    vfo_tx = 0;
                    while((PINC & (1<<PINC5)) && (PINC & (1<<PINC4)))
                    {
                        if(!(PIND & (1<<PIND2))) //++
                        {
                            while(!(PIND & (1<<PIND2)));
                            if(vfo_tx >= MAXVFO)
                            {
                                vfo_tx = 0;
                            }
                            else
                            {
                                vfo_tx++;
                            }
                        }
                        if(!(PIND & (1<<PIND3))) //--
                        {
                            while(!(PIND & (1<<PIND3)));
                            if(vfo_tx <= 0)
                            {
                                vfo_tx = MAXVFO;
                            }
                            else
                            {
                                vfo_tx--;
                            }
                        }
                        show_vfo(vfo_tx);
                    }
                    if(!(PINC & (1<<PINC4))) //CANCEL
                    {
                        while(!(PINC & (1<<PINC4)));
                    }
                    if(!(PINC & (1<<PINC5))) //OK
                    {
                        while(!(PINC & (1<<PINC5)));
                        split_mode = 1;
                        lcd_cls();
                        lcd_putstring(0, 0, "TX VFO=");
                        show_vfo(vfo_tx);
                        _delay_ms(1000);
                        lcd_cls();
                        exit_func = 1;
                    }
                }
            }
            if(!exit_func)
            {
                lcd_cls();
                lcd_putstring(0, 1, "SLEEP?");
                //Wait for key release
                while(!(PINC & (1<<PINC4)));
                //Wait for key
                //FUNC=NO, STEP=YES
                while((PINC & (1<<PINC4)) && (PINC & (1<<PINC5)));
                //FUNC = Cancel
                if(!(PINC & (1<<PINC4)))
                {
                    while(!(PINC & (1<<PINC4)));
                    lcd_cls();
                }
                //Set MUC to SLEEPMODE
                if(!(PINC & (1<<PINC5)))
                {
                    //Tuning step = 10Hz
                    cur_freq_shift = 2;
                    //Show tuning step
                    lcd_putstring(1, 5, "  ");
                    lcd_putstring(1, 5, freq_shift_str[cur_freq_shift]);
                    show_freq(freq1);
                    lcd_putstring(1, 0, "*SLEEP* ");
                    EICRA = 0;
                    EICRA |= (1<<ISC11) | (1<<ISC01);  //Interrupt to wake up device triggered on
                    //falling edge on PD2 and PD3
                    EIMSK |= (1<<INT0) | (1<<INT1);    //Enable external interrupt for INT0 and INT1
                    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
                    sleep_mode();
                    //Wake up, returning from ISR
                    lcd_putstring(1, 0, "        ");
                    show_step(cur_freq_shift);
                    EIMSK &= ~(1<<INT0); //Disable external interrupt for INT0 and INT1
                    EIMSK &= ~(1<<INT1);
                    exit_func = 1;
                }
            }
            lcd_cls();
            show_freq(freq1);
            show_step(cur_freq_shift);
        }
        if(scan_mode)
        {
            show_step(scan_step);
            switch(scan_mode)
            {
                case 1: freq1 += freq_shift[scan_step];
                set_frequency(freq1, interfreq, AD9835UPDATE);
                break;
                case 2: freq1 -= freq_shift[scan_step];
                set_frequency(freq1, interfreq, AD9835UPDATE);
                break;
            }
        }
        //Set Frequency-Shift for tuning
        if(!(PINC & (1<<PINC5))) //Frequency-Shift when tuning
        {
            //runsecsold1 = runsecs;
            runsecsold3 = runsecs;
            if(cur_freq_shift < MAXSHIFT)
            {
                cur_freq_shift++;
            }
            else
            {
                cur_freq_shift = 0;
            }
            while(!(PINC & (1<<PINC5)));
            scan_step = cur_freq_shift;
            lcd_putstring(1, 5, "  ");
            show_step(cur_freq_shift);
            idlesecs = runsecs;
        }
        //Reset shift to 10Hz after idle time (1 sec.)
        if(runsecsold3 + 1 < runsecs && cur_freq_shift != 2)
        {
            cur_freq_shift = 2;
            //Show tuning step
            lcd_putstring(1, 5, "   ");
            show_step(cur_freq_shift);
            runsecsold3 = runsecs;
        }
        //Show current VFO
        if(runsecs > runsecsold2 + 1)
        {
            if(displayed != 2)
            {
                lcd_putstring(1, 0, "     ");
                show_vfo(vfo_cnt);
                displayed = 2;
                runsecsold2 = runsecs;
            }
        }
        //Show battery voltage
        if(runsecs > runsecsold2 + 3 && displayed != 1)
        {
            //Check battery voltage with ADC
            adc_val = get_adc(0);
            voltage2 = (unsigned long) adc_val * 5 * 32 / 1024;
            lcd_putstring(1, 0, "     ");
            lcd_putnumber(1, 0, voltage2, -1, 1, 'l', 0);
            lcd_putstring(1, 4, "V");
            runsecsold2 = runsecs;
            displayed = 1;
        }
        if(split_mode) //SPLIT-MODE
        {
            if(!(PINC & (1<<PINC2))) //TX indicator hi
            {
                if(!tx_freq_set)
                {
                    set_frequency(vfo[vfo_tx], interfreq, AD9835UPDATE);
                    tx_freq_set = 1;
                }
            }
            else
            {
                if(tx_freq_set)
                {
                    set_frequency(vfo[vfo_cnt], interfreq, AD9835UPDATE);
                    tx_freq_set = 0;
                }
            }
        }
    }
    return 0;
}

Mounting a rod antenna to a homemade handheld QRP transceiver

When I started to recognize that going outside with a small handheld QRP SSB transceiver for the 20 meter band is more than just a test to find out doesn’t work at all, I conceived a more rugged mounting for the partable rod antenna. Due to the fact that this antenna (which now is about 220 centimeters long) exerts significant leverage to the BNC connector and thus to the cabinet of my transceiver. After 3 or 4 periods of outdoor usage I found that it had cut the front panel with the BNC socket from the transceiver’s inner cabinet frame. F…! (F…-word censored!)

The objective of a practical solution was to keep away excessive leverage force from the transceiver. The most practical way to solve this problem was to construct a simple mounting frame that could bear the force without leading it to the radio:

Antenna mounting frame for portable rod-antenna (C) by Peter Rachow- DK7IH
Antenna mounting frame for portable rod-antenna (C) by Peter Rachow- DK7IH

The holder is made of 0.8 mm Aluminium in u-shaped form where the radio fits in. The rig’s cabinet screws hinder the TRX from falling outside, a Velcro® strip fixes the radio inside the frame. On the backside of the frame I’ve attached a piece of aluminium pipe where the base of the antenna fits in. That’s all:

QRP SSB handheld tranceiver in mounting frame for portable rod-antenna (C) Peter Rachow - DK7IH
QRP SSB handheld tranceiver in mounting frame for portable rod-antenna (C) Peter Rachow – DK7IH

Easy and practical. That’s the way it’s got to be!

Annotation: I once again revised the antenna. The matching circuit was abolished. I now simply use a larger coil of about 55 turns of 1 mm diameter enameled wire on an 8.5 mm diameter PVC rod. Works great. Standing wave ratio is 1.1 to 1! 😉

73 de Peter (DK7IH)

(C) 2015 by Peter Rachow

Improved matching for 20-meter-band rod antenna

Recently I have revised the rod antenna for my handheld QRP SBB transceiver. The main objective was to simplify the matching circuit. As I’ve pointed out before in the antenna article, one of the major problems with shortened antennas is the low feed point impedance. The first version of the antenna matcher used a capacitor and a coil to form part of a PI-filter. This new circuit uses an autotransformer made of a linear coil.

Improved version of rod antenna for 20 meter handheld SSB QRP  transceiver (C) Peter Rachow (DK7IH)
Improved version of rod antenna for 20 meter handheld SSB QRP transceiver (C) Peter Rachow (DK7IH)

The tap is at about 1/4th of the total windings which transforms the feed point impedance of about 10 ohms to the coxial cable with 50 ohms. A 5 meter wire acting as the second part of the dipole should be used to increase performance of the rod antenna. All dimension concerning antenna length have remained unaltered, please see respective article!

An easy-to-build rod antenna for 14 MHz (20 meter band)

For my handheld QRP transceiver I have developed a rod antenna for outdoor use when cycling or hiking. In this article I’ll first give the reader some basic considerations on shortened antennas and afterwards the pracital consequences of these.

Shortened Antennas

A full-sized vertical antenna usually has got a length of a quarter wavelength. Some special constructions of a 1/2, 5/8 wavelength and others are also commonly known. But for a rod antenna the basic construction is a 1/4 wavelength based circuit. Full-size 1/4 wavelength antennas that have the correct length (1/4 multiplied by 0.95 shortening factor) have got a feed point impedance of between 40 to 50 ohms. So they can be directly connected to a 50 ohm coaxial cable. The problem: For 14 MHz this antenna would have an overall length of about 5 meters. Not very appropriate for a handheld transceiver except you were Arnold Schwarzenegger.. 😉

When the mechanical length of an antenna is shorter than 1/4 of the wavelength that it is desired for, a mismatch can be obeserved. The antenna not longer is resonant to the operating frequency. An additional capacitive reactance appears. This must be compensated by an inductive reactance. Therefore shortened antennas have an integrated coil either at the bottom (bottom loading coil), anywhere in the middle (center loading coil) or top (top loading coil).

Another problem must be faced: Feed point impedance is usually much lower than for a full size antenna. Sometimes only between 10 and 15 ohms. Thus some sort of impedance matching circuit must be integrated, too,

Last, degree of effiency will also decrease. So, don’t expect too much from a shortened antenna!

To sum up the things that have been mentioned before: We have to face 2 general electrical problems with short antennas:

  • A correct loading coil must be found, and
  • proper impedance matching must be performed.

Practical construction

Into my antenna I integrated 2 loading coils. One at the bottom to serve as an impedance matcher and one slightly below the center to compensate capacitive reactance.

The circuit:

A whip antenna for 14 MHz (C) Peter Rachow, DK7IH, 2015
A whip antenna for 14 MHz (C) Peter Rachow, DK7IH, 2015

The antenna is mounted to a standard male BNC-connector.

Mechanical deimensions and coil data:

Bottom coil (L2):

L2, the bottom coil, is 50 turns of 1 mm diameter enameled wire wound on a 8mm diameter plastc tubing from the local hardware store. From the bottom end of L2 a 120 pF capacitor is lead to the ground potential of the BNC plug. This, together with L2 sets up low pass filter serving as an impedance matcher.

Edit: Another impedance matching circuit I’ve described here.

The plastic tubing used for L1 and L2 is hollow. Into this tubing a 6mm diameter aluminium rod fits in exactly. The length of the rod between the two coils is 45 cm. Following is another piece of the plastic tubing carriyng L1.

Center coil (L1): L1 is 45 windings of 0.6 mm diameter enameled wire.

The top rod of the antenna is a telescopic antenna with an overall length of 120 cm that can be bought on the internet. It’s outer diameter is also 6 mm, so it also fits into the plastic rod.

Here are the pictures to make clearer how the antenna is constructed:

Whip antenna for 14 MHz (C) Peter Rachow, DK7IH, 2015
Rod antenna for 14 MHz (C) Peter Rachow, DK7IH, 2015
Full view of whip antenna for 14 MHz (C) Peter Rachow, DK7IH, 2015
Full view of rod antenna for 14 MHz (C) Peter Rachow, DK7IH, 2015

To increase the usually low degree of efficiency of such an antenna I have manufactured a sort of “counterpoise” that represents the 2nd part of a dipole : A 5 meter long insulated cable with a large crocodile clip that is clipped to the ground potential of the metal BNC connector.

Edit: In the meanwhile I have used this rod antenna for 14 MHz / 20 meters several times during outdoor activities. What I’d never have believed: I could make lots of QSOs with it! Particularly if you are in a high place (e. g. on a look-out) you can work distances of about 1000 to 3000 kilometers right with 4 watts out of your hands. Provided the station you’re answering is strong. Then there is a realistic chance that he might hear you with reasonable signal strengh.

A compact handheld QRP SSB transceiver for 14 MHz

by Peter Rachow (DK7IH)    => Zur deutschen Version dieses Artikels


Notice: Read about the software for this DDS-controlled transceiver here:

https://radiotransmitter.wordpress.com/2015/08/15/update-software-for-dds-controlled-qrp-ssb-handheld-transceiver-ad9835-atmega328p/


I have really been satisfied with my last QRP SSB rig. It performs very fine. But I wanted a transceiver still a little bit smaller. And it should be easier to set up the rig if you are outside to make quick QSOs. This and the fact that I came across a larger bunch of 9.832 MHz crystals which I thought could be an ideal basis for a ladder filter made me plan an even more compact rig for 20 m compared to the last one. Particularly for portable operation on holiday or when I am outdoor with my bicycle or hiking, I wanted a self-containing transceiver with on-board battery. So here it is…

A compact handheld SSB QRP transceiver for 14 MHz (20 meter band) by Peter Rachow (DK7IH)
A compact handheld SSB QRP transceiver for 14 MHz (20 meter band) by Peter Rachow (DK7IH)

Features:

  • Monoband SSB-Transceiver for 20 Meters/14 MHz.
  • Output: 4 to 5 Watts PEP
  • Frequency generation: DDS with AD9835 and ATMega8, LCD 2×8 Characters.
  • Transmitter: SSB Modulator for USB or LSB: NE602/SA612, 4-Pole-Ladder-Filter, TX-Mixer, NE602/SA612, Power amplifer: 3 stages, push-pull-final with 2 x 2SC2078.
  • Receiver: Singe conversion superhet, RF preamp with Dual-Gate MOSFET, RX-Mixer with NE602/SA612, 4-Pole-Ladder-Filter, Passive product detector, AF preamp, AF final amp with LM386. (See improved AGC circuit here!)
  • Built-in battery pack, also connectable to external power supply, 10-LED-bargraph-display for S and RF strength readout.

The size is about that of those older CB handhelds from the late 70s. It is abt. 18 cm long, 7 cm wide and 4.5 cm high. But don’ ask me for weight. 😉

The transceiver has, as mentioned before, a power output of 4 to 5 watts rf pep which I found sufficient for making contacts worldwide. Transmit power mainly depends  on the respective power supply you use. The radio can be run either on an integrated 12 V dc rechargeable battery (1Ah, composed of 10 AAA cells) or by connecting an external dc power supply of up to 14 volts. A three-position switch allows the user to select either internal or external power supply or completely switch the rig off.

Thus the little radio is very versatile for all kinds of portable operation. The antenna is connected via a standard bnc connector. I also designed a rod antenna that you can use if there is no possibility to erect a larger aerial. I have to admit that having a qso with the small handheld antenna ist a pretty ambitious. 😉 But in recent “The King of Spain”-Contest I could work with the rod antenna from a high place over a dozen stations.

With my delta loop the rig is absolutely amazing. During the recent weeks I worked (among others) the following prefixes:

SM, EA, F, 9A, 4X, LZ, I, SV9, J79, J49, JW9, S56, CY0, P40, G, YO, OH, W1, UA9, TA, PA, S57, GU, EK3, OX

And this all was done, except from the contact to P40FN, with 4 to 5 watts pep. Only for the QSO to Aruba I had to use the 60 watt linear amplifier. The pile-up was too hard. 😉

Basic design ideas:

To make the rig not too bulky I used a “sandwich construction” in an aluminium frame. The radio mainly consists of three layers:

  • The RF and AF unit (mainboard)
  • The battery-, AGC/Meter- and switching unit
  • The display and push-button unit

The RF unit:

RF unit of a compact handheld SSB QRP transceiver for 14 MHz (20 meter band) by Peter Rachow (DK7IH)
RF unit of a compact handheld SSB QRP transceiver for 14 MHz (20 meter band) by Peter Rachow (DK7IH)

On board here you can find the DDS-VFO, the whole receiver and transmitter circuits (including power amp), SSB modulator and demodulator.

The battery and AGC/Meter and switching unit:

AGC/Meter/Battery unit of a handheld compact SSB QRP transceiver for 14 MHz (20 meter band) by Peter Rachow (DK7IH)
AGC/Meter/Battery unit of a handheld compact SSB QRP transceiver for 14 MHz (20 meter band) by Peter Rachow (DK7IH)

This board is equipped with a set of 10 rechargeable batteries (1.2 volts each), the relay for transmit/receive switching and the LED-S/RF-meter circuit (LM3915) plus the AGC device. Above this section there is another layer which keeps the 2×8 line LCD, the up/down control switches for tuning (there is no rotator tuning knob), a button for setting the tuning frequency step, selecting the VFO (4 VFOs and 2 split VFOs are software defined) etc. It is  integrated in an extra housing mounted on top of the cabinet an connected via some cables. The controls are simple push-buttons. With one of these the microcontroller can be set into sleepmode to reduce the radio’s noise down to a minimum. The 1 inch loudspeaker is also mounted in here.

The circuitry itself is standard QRP design with the “usual suspects”. See the schematic to learn more:

Revised schematic of QRP SSB handheld transceiver for 14 MHz/20Meter by DK7IH (Peter Rachow)
Revised schematic of QRP SSB handheld transceiver for 14 MHz/20Meter by DK7IH (Peter Rachow)

The front panel looks like this:

Front panel of small handheld SSB QRP transceiver for 14 MHZ by Peter Rachow (DK7IH)
Front panel of small handheld SSB QRP transceiver for 14 MHZ by Peter Rachow (DK7IH)

Details are to be discussed in my next posting on this blog. Thanks for watching! 73 de Peter (DK7IH)

(C) 2015 Peter Rachow