The “Micro20 III” QRP SSB Transceiver for 14 MHz – The Software

/*****************************************************************/
/*             VFO for QRP SSB Nano-Transceiver 20m              */
/*                       "Micro20 III"                           */
/*                 w. ATMega168 and Si5351                       */
/*                    Display OLED SSD1306                       */
/*  ************************************************************ */
/*  MUC:              ATMEL AVR ATmega168, 8 MHz                 */
/*                                                               */
/*  Compiler:         GCC (GNU AVR C-Compiler)                   */
/*  Author:           Peter Rachow (DK7IH)                       */
/*  Last change:      OCT 2017                                   */
/*****************************************************************/

//PORTS
//OUTPUT

//INPUT
//PC0: =ADC0: Pull-up for key switches with various resistors against GND 
//PC1: =ADC1: AGC voltage for S-Meter
//PC2: =ADC2: Voltage measurement
//PC3: =ADC3: TX PWR meter



//PD1: TX/RX indicator
//PD5, PD6: Rotary encoder

//TWI
//PC4=SDA, PC5=SCL: I²C-Bus lines: 

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/sleep.h>
#include <avr/eeprom.h>
#include <util/delay.h>
#include <util/twi.h>

#undef F_CPU 
#define F_CPU 80000000

/////////////////////
//Defines for Si5351
/////////////////////
#define SI5351_ADDRESS 0xC0 // 0b11000000 for my module. Others may vary! The 0x60 did NOT work with my module!

//Set of Si5351A register addresses
#define CLK_ENABLE_CONTROL       3
#define PLLX_SRC				15
#define CLK0_CONTROL            16 
#define CLK1_CONTROL            17
#define CLK2_CONTROL            18
#define SYNTH_PLL_A             26
#define SYNTH_PLL_B             34
#define SYNTH_MS_0              42
#define SYNTH_MS_1              50
#define SYNTH_MS_2              58
#define PLL_RESET              177
#define XTAL_LOAD_CAP          183

//SI5351 Declarations & frequency
void si5351_write(int, int);
void si5351_start(void);
void si5351_set_freq(int, unsigned long);
void set_oscillators(int, unsigned long, int);
unsigned long scan_frequency(unsigned long, unsigned long, int, int);     

//////////////////////////////////////
//   L   C   D   
//////////////////////////////////////


// Font 5x8 for OLED
const char fontchar[485] = {
0x00,0x00,0x00,0x00,0x00, // 20 space ASCII table for NOKIA LCD: 96 rows * 5 bytes= 480 bytes
0x00,0x00,0x5f,0x00,0x00, // 21 ! Note that this is the same set of codes for character you
0x00,0x07,0x00,0x07,0x00, // 22 " would find on a HD44780 based character LCD.
0x14,0x7f,0x14,0x7f,0x14, // 23 # Also, given the size of the LCD (84 pixels by 48 pixels),
0x24,0x2a,0x7f,0x2a,0x12, // 24 $ the maximum number of characters per row is only 14.
0x23,0x13,0x08,0x64,0x62, // 25 %
0x36,0x49,0x55,0x22,0x50, // 26 &
0x00,0x05,0x03,0x00,0x00, // 27 '
0x00,0x1c,0x22,0x41,0x00, // 28 (
0x00,0x41,0x22,0x1c,0x00, // 29 )
0x14,0x08,0x3e,0x08,0x14, // 2a *
0x08,0x08,0x3e,0x08,0x08, // 2b +
0x00,0x50,0x30,0x00,0x00, // 2c ,
0x08,0x08,0x08,0x08,0x08, // 2d -
0x00,0x60,0x60,0x00,0x00, // 2e .
0x20,0x10,0x08,0x04,0x02, // 2f /
0x3e,0x51,0x49,0x45,0x3e, // 30 0
0x00,0x42,0x7f,0x40,0x00, // 31 1
0x42,0x61,0x51,0x49,0x46, // 32 2
0x21,0x41,0x45,0x4b,0x31, // 33 3
0x18,0x14,0x12,0x7f,0x10, // 34 4
0x27,0x45,0x45,0x45,0x39, // 35 5
0x3c,0x4a,0x49,0x49,0x30, // 36 6
0x01,0x71,0x09,0x05,0x03, // 37 7
0x36,0x49,0x49,0x49,0x36, // 38 8
0x06,0x49,0x49,0x29,0x1e, // 39 9
0x00,0x36,0x36,0x00,0x00, // 3a :
0x00,0x56,0x36,0x00,0x00, // 3b ;
0x08,0x14,0x22,0x41,0x00, // 3c <
0x14,0x14,0x14,0x14,0x14, // 3d =
0x00,0x41,0x22,0x14,0x08, // 3e >
0x02,0x01,0x51,0x09,0x06, // 3f ?
0x32,0x49,0x79,0x41,0x3e, // 40 @
0x7e,0x11,0x11,0x11,0x7e, // 41 A
0x7f,0x49,0x49,0x49,0x36, // 42 B
0x3e,0x41,0x41,0x41,0x22, // 43 C
0x7f,0x41,0x41,0x22,0x1c, // 44 D
0x7f,0x49,0x49,0x49,0x41, // 45 E
0x7f,0x09,0x09,0x09,0x01, // 46 F
0x3e,0x41,0x49,0x49,0x7a, // 47 G
0x7f,0x08,0x08,0x08,0x7f, // 48 H
0x00,0x41,0x7f,0x41,0x00, // 49 I
0x20,0x40,0x41,0x3f,0x01, // 4a J
0x7f,0x08,0x14,0x22,0x41, // 4b K
0x7f,0x40,0x40,0x40,0x40, // 4c L
0x7f,0x02,0x0c,0x02,0x7f, // 4d M
0x7f,0x04,0x08,0x10,0x7f, // 4e N
0x3e,0x41,0x41,0x41,0x3e, // 4f O
0x7f,0x09,0x09,0x09,0x06, // 50 P
0x3e,0x41,0x51,0x21,0x5e, // 51 Q
0x7f,0x09,0x19,0x29,0x46, // 52 R
0x46,0x49,0x49,0x49,0x31, // 53 S
0x01,0x01,0x7f,0x01,0x01, // 54 T
0x3f,0x40,0x40,0x40,0x3f, // 55 U
0x1f,0x20,0x40,0x20,0x1f, // 56 V
0x3f,0x40,0x38,0x40,0x3f, // 57 W
0x63,0x14,0x08,0x14,0x63, // 58 X
0x07,0x08,0x70,0x08,0x07, // 59 Y
0x61,0x51,0x49,0x45,0x43, // 5a Z
0x00,0x7f,0x41,0x41,0x00, // 5b [
0x02,0x04,0x08,0x10,0x20, // 5c Yen Currency Sign
0x00,0x41,0x41,0x7f,0x00, // 5d ]
0x04,0x02,0x01,0x02,0x04, // 5e ^
0x40,0x40,0x40,0x40,0x40, // 5f _
0x00,0x01,0x02,0x04,0x00, // 60 `
0x20,0x54,0x54,0x54,0x78, // 61 a
0x7f,0x48,0x44,0x44,0x38, // 62 b
0x38,0x44,0x44,0x44,0x20, // 63 c
0x38,0x44,0x44,0x48,0x7f, // 64 d
0x38,0x54,0x54,0x54,0x18, // 65 e
0x08,0x7e,0x09,0x01,0x02, // 66 f
0x0c,0x52,0x52,0x52,0x3e, // 67 g
0x7f,0x08,0x04,0x04,0x78, // 68 h
0x00,0x44,0x7d,0x40,0x00, // 69 i
0x20,0x40,0x44,0x3d,0x00, // 6a j
0x7f,0x10,0x28,0x44,0x00, // 6b k
0x00,0x41,0x7f,0x40,0x00, // 6c l
0x7c,0x04,0x18,0x04,0x78, // 6d m
0x7c,0x08,0x04,0x04,0x78, // 6e n
0x38,0x44,0x44,0x44,0x38, // 6f o
0x7c,0x14,0x14,0x14,0x08, // 70 p
0x08,0x14,0x14,0x18,0x7c, // 71 q
0x7c,0x08,0x04,0x04,0x08, // 72 r
0x48,0x54,0x54,0x54,0x20, // 73 s
0x04,0x3f,0x44,0x40,0x20, // 74 t
0x3c,0x40,0x40,0x20,0x7c, // 75 u
0x1c,0x20,0x40,0x20,0x1c, // 76 v
0x3c,0x40,0x30,0x40,0x3c, // 77 w
0x44,0x28,0x10,0x28,0x44, // 78 x
0x0c,0x50,0x50,0x50,0x3c, // 79 y
0x44,0x64,0x54,0x4c,0x44, // 7a z
0x00,0x08,0x36,0x41,0x00, // 7b <
0x00,0x00,0x7f,0x00,0x00, // 7c |
0x00,0x41,0x36,0x08,0x00, // 7d >
0x10,0x08,0x08,0x10,0x08, // 7e Right Arrow ->
0x78,0x46,0x41,0x46,0x78, // 7f Left Arrow <-
0x00,0x06,0x09,0x09,0x06};  // 80 °

///////////////////////////
//     DECLARATIONS
///////////////////////////
//
//I²C
void TWIInit(void);
void TWIStart(void);
void TWIStop(void);
uint8_t TWIReadACK(void);
uint8_t TWIReadNACK(void);
uint8_t TWIGetStatus(void);

//OLED
void oled_command(int value);
void oled_init(void);
void oled_write_byte(int col, int page, int val);
void oled_cls(void);
void oled_putchar1(int, int, char, int);
void oled_putchar2(int, int, char, int);
void oled_putstring(int, int, char *, char, int);
void oled_putnumber(int, int, long, int, int, int);
void oled_clear_section(int, int, int);
int xp2(int xp);

//String
int int2asc(long num, int dec, char *buf, int buflen);
int strlen(char *s);

//Data display functions
void show_frequency(long);
void show_frequency_cls(void);
void show_tstep(int, int);
void show_sideband(int, int);
void show_voltage(int);
void show_meter(int);
void draw_meter_scale(int meter_type);
void show_store(int, int, unsigned long);
void show_txrx(int);
void show_split(int);
void show_split2(int);
void show_rcl(int);


//ADC
int get_adc(int);
int get_keys(void);

//EEPROM
void store_frequency(unsigned long);
unsigned long load_frequency(void);

//MISC
int main(void);
unsigned int menu(void);
int set_lo_frequencies(void);

/////////////////////////////////////
//   Global constants & variables
/////////////////////////////////////
int laststate = 0; //Last state of rotary encoder
int tuningknob = 0;
int split = 0;
	
//Tuning steps available
#define MAXTSTEPS 6 //Highest index of tsteps = n-1
#define STDTSTEP 2 //Standard tuning step
int n_tstep[] = {10, 50, 100, 250, 500, 1000, 5000};
int cur_tstep = STDTSTEP;

//SIDEBAND
#define MAXMODES 2
int sideband = 0;  //Current sideband in use USB=0, LSB=1
unsigned long f_bfo[] = {10697600, 10692700}; //LO FREQUENCIES 10.7MHz

//METER
int sv_old = 0;

///////////////////////////
//
//         TWI
//
///////////////////////////

void twi_init(void)
{
    //set SCL to 400kHz
    TWSR = 0x00;
    TWBR = 0x0C;
	
    //enable TWI
    TWCR = (1<<TWEN);
}

//Send start signal
void twi_start(void)
{
    TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
    while ((TWCR & (1<<TWINT)) == 0);
}

//send stop signal
void twi_stop(void)
{
    TWCR = (1<<TWINT)|(1<<TWSTO)|(1<<TWEN);
}

void twi_write(uint8_t u8data)
{
    TWDR = u8data;
    TWCR = (1<<TWINT)|(1<<TWEN);
    while ((TWCR & (1<<TWINT)) == 0);
}

uint8_t TWIGetStatus(void)
{
    uint8_t status;
    //mask status
    status = TWSR & 0xF8;
    return status;
}

////////////////////////////////
//
// Si5351A commands
//
///////////////////////////////
void si5351_write(int reg_addr, int reg_value)
{
   	 
   twi_start();
   twi_write(SI5351_ADDRESS);
   twi_write(reg_addr);
   twi_write(reg_value);
   twi_stop();
} 

// Set PLLs (VCOs) to internal clock rate of 900 MHz
// Equation fVCO = fXTAL * (a+b/c) (=> AN619 p. 3
void si5351_start(void)
{
  unsigned long a, b, c;
  unsigned long p1, p2;//, p3;
  
  
  // Init clock chip
  si5351_write(XTAL_LOAD_CAP, 0xD2);      // Set crystal load capacitor to 10pF (default), 
                                          // for bits 5:0 see also AN619 p. 60
  si5351_write(CLK_ENABLE_CONTROL, 0x00); // Enable all outputs
  si5351_write(CLK0_CONTROL, 0x0F);       // Set PLLA to CLK0, 8 mA output
  si5351_write(CLK1_CONTROL, 0x2F);       // Set PLLB to CLK1, 8 mA output
  si5351_write(CLK2_CONTROL, 0x2F);       // Set PLLB to CLK2, 8 mA output
  si5351_write(PLL_RESET, 0xA0);          // Reset PLLA and PLLB

  // Set VCOs of PLLA and PLLB to 900 MHz
  a = 36;           // Division factor 900/25 MHz
  b = 0;            // Numerator, sets b/c=0
  c = 1048757;      //Max. resolution, but irrelevant in this case (b=0)

  //Formula for splitting up the numbers to register data, see AN619
  p1 = 128 * a + (unsigned long) (128 * b / c) - 512;
  p2 = 128 * b - c * (unsigned long) (128 * b / c);
  //p3  = c;

  
  //Write data to registers PLLA and PLLB so that both VCOs are set to 900MHz intermal freq
  si5351_write(SYNTH_PLL_A, 0xFF);
  si5351_write(SYNTH_PLL_A + 1, 0xFF);
  si5351_write(SYNTH_PLL_A + 2, (p1 & 0x00030000) >> 16);
  si5351_write(SYNTH_PLL_A + 3, (p1 & 0x0000FF00) >> 8);
  si5351_write(SYNTH_PLL_A + 4, (p1 & 0x000000FF));
  si5351_write(SYNTH_PLL_A + 5, 0xF0 | ((p2 & 0x000F0000) >> 16));
  si5351_write(SYNTH_PLL_A + 6, (p2 & 0x0000FF00) >> 8);
  si5351_write(SYNTH_PLL_A + 7, (p2 & 0x000000FF));

  si5351_write(SYNTH_PLL_B, 0xFF);
  si5351_write(SYNTH_PLL_B + 1, 0xFF);
  si5351_write(SYNTH_PLL_B + 2, (p1 & 0x00030000) >> 16);
  si5351_write(SYNTH_PLL_B + 3, (p1 & 0x0000FF00) >> 8);
  si5351_write(SYNTH_PLL_B + 4, (p1 & 0x000000FF));
  si5351_write(SYNTH_PLL_B + 5, 0xF0 | ((p2 & 0x000F0000) >> 16));
  si5351_write(SYNTH_PLL_B + 6, (p2 & 0x0000FF00) >> 8);
  si5351_write(SYNTH_PLL_B + 7, (p2 & 0x000000FF));

}

void si5351_set_freq(int synth, unsigned long freq)
{
  unsigned long  a, b, c = 1048575;
  unsigned long f_xtal = 25000000;
  double fdiv = (double) (f_xtal * 36) / freq; //division factor fvco/freq (will be integer part of a+b/c)
  double rm; //remainder
  unsigned long p1, p2;
  
  a = (unsigned long) fdiv;
  rm = fdiv - a;  //(equiv. to fractional part b/c)
  b = rm * c;
  p1  = 128 * a + (unsigned long) (128 * b / c) - 512;
  p2 = 128 * b - c * (unsigned long) (128 * b / c);
    
  //Write data to multisynth registers of synth n
  si5351_write(synth, 0xFF);      //1048757 MSB
  si5351_write(synth + 1, 0xFF);  //1048757 LSB
  si5351_write(synth + 2, (p1 & 0x00030000) >> 16);
  si5351_write(synth + 3, (p1 & 0x0000FF00) >> 8);
  si5351_write(synth + 4, (p1 & 0x000000FF));
  si5351_write(synth + 5, 0xF0 | ((p2 & 0x000F0000) >> 16));
  si5351_write(synth + 6, (p2 & 0x0000FF00) >> 8);
  si5351_write(synth + 7, (p2 & 0x000000FF));
}

void set_oscillators(int txrx, unsigned long vfo_freq, int bfo)
{
    if(txrx) //TX, cause PIND1 is 1
	{
	    si5351_set_freq(SYNTH_MS_0, vfo_freq + f_bfo[bfo]);		   
		si5351_set_freq(SYNTH_MS_1, f_bfo[bfo]);
	}	
	else //RX
	{
	   si5351_set_freq(SYNTH_MS_0, f_bfo[bfo]);
	   si5351_set_freq(SYNTH_MS_1, vfo_freq + f_bfo[bfo]);   
	}    	
		
}

unsigned long scan_frequency(unsigned long f1, unsigned long f2, int ts, int bfo)     
{
     unsigned long f;
     int key;
     int sval = 0;
     
     if(f1 == f2)
     {
		 return 0;
	 }	 
         
     if(f1 > f2)
     {
		 f = f1;
		 f1 = f2;
		 f2 = f;
	 }	 
          
     while(1)
     {
         for(f = f1; f <= f2; f += ts)
         {
		     set_oscillators(0, f, bfo);
		     show_frequency(f);
		     key = get_keys();
		     if(key == 2)
		     {
				 return f;
		     }	 
		     
		     if(key == 1)
		     {
				 return 0;
		     }	 
		     
		     sval = 390 - get_adc(1); //ADC voltage on ADC3 SVAL
		 	 show_meter(sval >> 2); //S-Meter
		 	 
		 	 //Hold, when S value hi!
		 	 if((sval >> 2) > 60)
		 	 {
				 _delay_ms(200);
			 }	 
		 	 

		 }    
	 }	 
	 return 0;
}

////////////////////////////////
//
// OLED commands
//
///////////////////////////////

//Send comand to OLED
void oled_command(int value)
{
   twi_start();
   twi_write(0x78); //Device address
   twi_write(0x00);
   twi_write(value);
   twi_stop();
} 

//Initialize OLED
void oled_init(void)
{
    oled_command(0xae);
	
    oled_command(0xa8);//Multiplex ratio
    oled_command(0x3F);
	
    oled_command(0xd3);
    oled_command(0x00);
    oled_command(0x40);
    oled_command(0xa0);
    oled_command(0xa1);
	
	oled_command(0x20); //Adressing mode
    oled_command(0x00); //HOR
	
    oled_command(0xc0);
    oled_command(0xc8);
    oled_command(0xda);
    oled_command(0x12);
    oled_command(0x81);
    oled_command(0xfF);
    oled_command(0xa4); //Display ON with RAM content
    oled_command(0xa6); //Normal display (Invert display = A7)
    oled_command(0xd5);
    oled_command(0x80);
    oled_command(0x8d);
    oled_command(0x14);
	//oled_command(0x40); //Set display start line
    oled_command(0xAF); //Display ON
   
} 

//Print character in normal size
//Write 8 vertical bits to given col (0..127) and page (0..7)
void oled_write_byte(int col, int page, int val)
{
    int t1;
	
	oled_command(0x21); //COL
	oled_command(col); 
	oled_command(col); 
	
	oled_command(0x22); //PAGE
	oled_command(page); 
	oled_command(page); 
		
    twi_start();
	twi_write(0x78);
	twi_write(0x40); //Data
    for(t1 = 0; t1 < 7; t1++)
    {
        twi_write(val);
    }
	twi_stop();
}

//Clear screen
void oled_cls(void)
{

    int x, y;
	for(x = 0; x < 128; x++)
	{
	    for(y = 0; y < 8; y++)
		{
		    oled_write_byte(x, y, 0);
		}
    }	

}

//Print one character in normal size to OLED
void oled_putchar1(int col, int row, char ch1, int inv)
{ 
    int p, t1;
    char ch2;
	int c = col;
	    
    p = (5 * ch1) - 160;
    for(t1 = 0; t1 < 5; t1++)
    { 
	    if(!inv)
		{
	        ch2 = fontchar[p + t1];
		}
		else
		{
	        ch2 = ~fontchar[p + t1];
		}
		
        oled_write_byte(c++, row, ch2);
    }
	
	if(!inv)
	{
	    oled_write_byte(c, row, 0x00);
	}
	else
	{
	    oled_write_byte(c, row, 0xFF);
	}
    
}

//2^x
int xp2(int xp)
{
    int t1, r = 1;
    for(t1 = 0; t1 < xp; t1++)
    {
        r <<= 1;
    }
    return r;

}


//Print character in double size
void oled_putchar2(int col, int row, char ch1, int inv)
{ 
    int p, t1, t2, x;
	int b, b1, b2; 
    char colval;
	   
    p = (5 * ch1) - 160;
    	
	for(t2 = 0; t2 < 6; t2++)
    { 
	    //Get vertical byte data of char
		if(!inv)
		{
	        colval = fontchar[p];
			if(t2 == 5)
			{
			    colval = 0;
			}
		}
		else
		{
	        colval = ~fontchar[p];
			if(t2 == 5)
			{
			    colval = 255;
			}
		}
	  		
		b = 0;
		x = 1;
        
		for(t1 = 0; t1 < 7; t1++)
        {
            if(colval & x)
            {
	            b += xp2(t1 * 2);
	            b += xp2(t1 * 2 + 1);
            }
            x <<= 1;
	    }
    
        b1 = b & 0xFF; //Lower byte
        b2 = (b & 0xFF00) >> 8; //Upper byte
		
		//Print data to screen
		oled_write_byte(col + t2 * 2, row, b1);
		oled_write_byte(col + t2 * 2, row + 1, b2);
		oled_write_byte(col + t2 * 2 + 1, row, b1);
		oled_write_byte(col + t2 * 2 + 1, row + 1, b2);
		p++;
	}	
	
}

//Print string in given size
//lsize=0 => normal height, lsize=1 => double height
void oled_putstring(int col, int row, char *s, char lsize, int inv)
{
    int c = col;
	
	while(*s)
	{
	    if(!lsize)
		{
	        oled_putchar1(c, row, *s++, inv);
		}
        else
        {
            oled_putchar2(c, row, *s++, inv);
		}	
		c += (lsize + 1) * 6;
	}
}

//Print an integer/long to OLED
void oled_putnumber(int col, int row, long num, int dec, int lsize, int inv)
{
    char *s = malloc(16);
	if(s != NULL)
	{
	    int2asc(num, dec, s, 16);
	    oled_putstring(col, row, s, lsize, inv);
	    free(s);
	}	
}

void oled_clear_section(int x1, int x2, int row)
{
    int t1;
	for(t1 = x1; t1 < x2; t1++)
	{
	    oled_write_byte(t1, row, 0);	
	}

}

/////////////////////////////////
//
// STRING FUNCTIONS
//
////////////////////////////////
//INT 2 ASC
int int2asc(long num, int dec, char *buf, int buflen)
{
    int i, c, xp = 0, neg = 0;
    long n, dd = 1E09;

    if(!num)
	{
	    *buf++ = '0';
		*buf = 0;
		return 1;
	}	
		
    if(num < 0)
    {
     	neg = 1;
	    n = num * -1;
    }
    else
    {
	    n = num;
    }

    //Fill buffer with \0
    for(i = 0; i < 12; i++)
    {
	    *(buf + i) = 0;
    }

    c = 9; //Max. number of displayable digits
    while(dd)
    {
	    i = n / dd;
	    n = n - i * dd;
	
	    *(buf + 9 - c + xp) = i + 48;
	    dd /= 10;
	    if(c == dec && dec)
	    {
	        *(buf + 9 - c + ++xp) = '.';
	    }
	    c--;
    }

    //Search for 1st char different from '0'
    i = 0;
    while(*(buf + i) == 48)
    {
	    *(buf + i++) = 32;
    }

    //Add minus-sign if neccessary
    if(neg)
    {
	    *(buf + --i) = '-';
    }

    //Eleminate leading spaces
    c = 0;
    while(*(buf + i))
    {
	    *(buf + c++) = *(buf + i++);
    }
    *(buf + c) = 0;
	
	return c;
}

//STRLEN
int strlen(char *s)
{
   int t1 = 0;

   while(*(s + t1++));

   return (t1 - 1);
}

/****************************/
//
//  DATA DISPLAY FUNCTIONS
//
/****************************/

//Current frequency (double letter height)
void show_frequency(long f)
{
    oled_putnumber(15, 3, (long) f / 10, 2, 1, 0);
}

void show_frequency_cls(void)
{
    oled_clear_section(0, 127, 3);
    oled_clear_section(0, 127, 4);
}

//Current tuning step (Page 0)
void show_tstep(int n_step, int invert)
{
	int xpos = 0, xlen = 5;
    char *s_tstep[] = {" 10Hz", " 50Hz", "100Hz", "250Hz", "500Hz", " 1kHz", " 5kHz"};
		
	//Clear section of LCD
	oled_clear_section(xpos * 6, xpos + xlen * 6, 0);
	
	//Write string to position
	oled_putstring(0, 0, s_tstep[n_step], 0, invert);
}

void show_sideband(int sb, int invert)
{
	int xpos = 6, xlen = 3;
	char *sidebandstr[MAXMODES + 1] = {"USB", "LSB"};
		
	//Clear section of LCD
	oled_clear_section(xpos * 6 , xpos + xlen * 6, 0);
	
	//Write string to position
	oled_putstring(xpos * 6, 0, sidebandstr[sb], 0, invert);
}

void show_store(int done, int invert, unsigned long f)
{
	int xpos = 10;	
	if(done)
	{
		oled_putnumber(62, 5, (long) f / 10, 2, 0, 0);
		oled_putstring(xpos * 6, 0, "STO", 0, invert);
		return;
	}	
	oled_putstring(xpos * 6, 0, "STO", 0, invert);
}

void show_voltage(int v1)
{
    char *buf;
	int t1, p;
	
	oled_clear_section(0, 30, 1);
	
	buf = malloc(10);
	//Init buffer string
	for(t1 = 0; t1 < 10; t1++)
	{
	    *(buf + t1) = 0;
	}
    p = int2asc(v1, 1, buf, 6) * 6;
    oled_putstring(0, 1, buf, 0, 0);
	oled_putchar1(p, 1, 'V', 0);
	free(buf);
}

void show_txrx(int status)
{
	int xpos = 19;
	
	//Write string to position
	if(status)
	{
	    oled_putstring(xpos * 6, 1, "TX", 0, 1);
	}
	else    
	{
	    oled_putstring(xpos * 6, 1, "RX", 0, 0);
	}
	
}	
//S-Meter bargraph (Page 6)
void show_meter(int sv0)
{
    int t1, sv;
	
	sv = sv0;
	
    if(sv > 120)
	{
	    sv = 120;
	}
		
	//Clear bar graph partly, when new s-val smaller than previous
    if(sv < sv_old)
    {
	     //Clear s-meter
	     for(t1 = sv; t1 < 128; t1 += 3)//
	     {
	          oled_write_byte(t1, 6, 0);
	     }	
         sv_old = sv;
         return;
    }
   
    //Draw bar graph
	for(t1 = 0; t1 < sv; t1 += 3)//
	{
	    oled_write_byte(t1, 6, 0x1E);
	}	
    
    sv_old = sv;
}

void draw_meter_scale(int meter_type)
{
    if(!meter_type)
    {
        oled_putstring(0, 7, "S1 S3 S5 S7 S9 +  ", 0, 0);
    }
    else
    {
        oled_putstring(0, 7, "0  1W   2W   3W   ", 0, 0);
    }
}

void show_split(int stat)
{
	int xpos = 18, xlen = 5;
			
	//Clear section of LCD
	oled_clear_section(xpos * 6 , xpos + xlen * 6, 0);
	
	//Write string to position
	oled_putstring(xpos * 6, 0, "SPL", 0, stat);
	
}

void show_split2(int stat)
{
	int xpos = 6, xlen = 5;
			
	//Clear section of LCD
	oled_clear_section(xpos * 6 , xpos + xlen * 6, 1);
	
	//Write string to position
	if(stat)
	{
	    oled_putstring(xpos * 6, 1, "SPLIT", 0, 1);
	}
	else
	{
		oled_putstring(xpos * 6, 1, "     ", 0, 0);    
	}	
	
}

void show_rcl(int stat)
{
	int xpos = 14, xlen = 3;
			
	//Clear section of LCD
	oled_clear_section(xpos * 6 , xpos + xlen * 6, 0);
	
	//Write string to position
	oled_putstring(xpos * 6, 0, "RCL", 0, stat);
	
}

void show_scan(int stat)
{

	int xpos = 13, xlen = 3;
			
	//Clear section of LCD
	oled_clear_section(xpos * 6 , xpos + xlen * 6, 1);
	
	//Write string to position
	oled_putstring(xpos * 6, 1, "SCAN", 0, stat);
	
}

////////////////////////////////////////////////////
//               INTERRUPT HANDLERS
////////////////////////////////////////////////////
//Rotary encoder
ISR(PCINT2_vect)
{ 
    
    int gray = (PIND & 0x60) >> 5;           // Read PD5 and PD6
	
    int state = (gray >> 1) ^ gray;         // Convert from Gray code to binary

    if (state != laststate)                //Compare states
    {        
        tuningknob += ((laststate - state) & 3) - 2; // Results in -1 or +1
        laststate = state;
    } 
	PCIFR |=  (1 << PCIF0); // Clear pin change interrupt flag.
}

//////////////////////
//
//   A   D   C   
//
/////////////////////
//Read ADC value
int get_adc(int adc_channel)
{
	
	int adc_val = 0;
	
	ADMUX = (1<<REFS0) + adc_channel;     // Kanal adcmode aktivieren
    _delay_ms(3);
	
    ADCSRA |= (1<<ADSC);
    //while(ADCSRA & (1<<ADSC));
	_delay_ms(3);
	
	adc_val = ADCL;
    adc_val += ADCH * 256;   
	
	return adc_val;
	
}	

//Read keys via ADC0
int get_keys(void)
{

    int key_value[] = {151, 241};  //PCB
    //151 left single push button #1, 241 = right button from rotator switch #2, 
    	
    int t1;
    int adcval = get_adc(0);
        
    //TEST display of ADC value 
    //oled_putstring(0, 5, "----", 0, 0);    
    //oled_putnumber(0, 5, adcval, -1, 0, 0);    
    
    if(adcval < 110 && adcval > 106) //Both buttons pressed simultanously PCB
    {
		return 99;
	}
		
    for(t1 = 0; t1 < 2; t1++)
    {
        if(adcval > key_value[t1] - 10 && adcval < key_value[t1] + 10)
        {
             return t1 + 1;
        }
    }
    return 0;
}

//////////////////////////////
//
//    EEPROM-Functions
//
//////////////////////////////

//Load and store frequency from EEPROM based on VFO
unsigned long load_frequency(void)
{
    unsigned long rf;
    unsigned char hmsb, lmsb, hlsb, llsb;
    int start_adr = 0;
		
    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);
	sei();
	
    rf = (unsigned long) 16777216 * hmsb + (unsigned long) 65536 * hlsb + (unsigned int) 256 * lmsb + llsb;
		
	return rf;
	
}


void store_frequency(unsigned long f)
{
    unsigned long hiword, loword;
    unsigned char hmsb, lmsb, hlsb, llsb;
	
    int start_adr = 0;
    
	cli();
    hiword = f >> 16;
    loword = f - (hiword << 16);
    hmsb = hiword >> 8;
    hlsb = hiword - (hmsb << 8);
    lmsb = loword >> 8;
    llsb = loword - (lmsb << 8);

    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();	
	
}

//////////////////////////////
//
//    M   E   N   U
//
//////////////////////////////
unsigned int menu(void)
{
	int key = 0;
    int store = 0;
    int rcl = 0;	
    int split2;	
    int scan2 = 0;
	while(get_keys());
	show_tstep(cur_tstep, 1);
	
	//TUNING STEP
    while(!key)
	{
		if(tuningknob > 2)  
		{    
		    if(cur_tstep < MAXTSTEPS)
		    {
			     cur_tstep++;
			}
			else     
			{
				 cur_tstep = 0;
			}	 
			tuningknob = 0;
			show_tstep(cur_tstep, 1);
		}
		
		if(tuningknob < -2 )
		{
		    if(cur_tstep > 0)
		    {
			     cur_tstep--;
			}
			else     
			{
				 cur_tstep = MAXTSTEPS;
			}	 
			tuningknob = 0;
			show_tstep(cur_tstep, 1);
		}
		key = get_keys();
	}	
	show_tstep(cur_tstep, 0);
	
	if(key == 2)
	{
		return 1;
	}
	while(get_keys());
	//////////////////////////
			
	//SIDEBAND SET	
	key = 0;	
	show_sideband(sideband, 1);
	while(!key)
	{
		if(tuningknob > 2 || tuningknob < -2)  
		{    
		    if(!sideband)
		    {
			     sideband = 1;
			}
			else     
			{
				 sideband = 0;
			}	 
			tuningknob = 0;
			show_sideband(sideband, 1);
			si5351_set_freq(SYNTH_MS_0, f_bfo[sideband]);
		}
		key = get_keys();
	}	
	show_sideband(sideband, 0);
	
	if(key == 2)
	{
		return 2;
	}	
	while(get_keys());
	////////////////////////////
		
	//STORE CURRENT QRG
	key = 0;
	store = 1;	
	show_store(0, store, 0); 
	while(!key)
	{
		if(tuningknob > 2 || tuningknob < -2)  
		{    
		    if(!store)
		    {
			     store = 1;
			}
			else     
			{
				 store = 0;
			}	 
			tuningknob = 0;
			show_store(0, store, 0);
		}
		key = get_keys();
	}	
	
	if(key == 2)
	{
	    return 4;
	}	
	
	show_store(0, 0, 0);	
	while(get_keys());
    ////////////////////////
    
	//Recall stored freuency
	key = 0;
	rcl = 1;
	show_rcl(rcl); 
	while(!key)
	{
		if(tuningknob > 2 || tuningknob < -2)  
		{    
		    if(!rcl)
		    {
			     rcl = 1;
			}
			else     
			{
				 rcl = 0;
			}	 
			tuningknob = 0;
			show_rcl(rcl); 
		}
		key = get_keys();
	}	
	
	show_rcl(0); 
	
	if(key == 1)
	{
		rcl = 0;
	}
			
	if(key == 2 && rcl)
	{
		return 6;
	}	
	while(get_keys());
    //////////////////////
		
	//SPLITMODE on/off
	key = 0;
	split2 = 1;
	show_split(split2); 
	while(!key)
	{
		if(tuningknob > 2 || tuningknob < -2)  
		{    
		    if(!split2)
		    {
			     split2 = 1;
			}
			else     
			{
				 split2 = 0;
			}	 
			tuningknob = 0;
			show_split(split2);
		}
		key = get_keys();
	}	
	
	show_split(0);  
	
	if(key == 2)
	{
	    if(split2)
	    {
			split = 1;
			show_split2(split);  
		    return 5;
	    }	
	    else
	    {
			split = 0;
			show_split2(split);  
	        return 0;
	    }	
    }	
    
    while(get_keys());
	
	
	//SCAN on/off
	key = 0;
	scan2 = 1;
	show_scan(scan2); 
	while(!key)
	{
		if(tuningknob > 2 || tuningknob < -2)  
		{    
		    if(!scan2)
		    {
			     scan2 = 1;
			}
			else     
			{
				 scan2 = 0;
			}	 
			tuningknob = 0;
			show_scan(scan2);
		}
		key = get_keys();
	}	
		
		
	if(key == 2)
	{
	    if(scan2)
	    {
			return 7;
	    }	
	    else
	    {
			return 0;
	    }	
    }	
    
    while(get_keys());
		
	return 0;
		
}	

int set_lo_frequencies(void)
{
	int key = 0;
		
    //LO FREQ USB
	key = 0;	
	show_frequency(f_bfo[0]);
	oled_putstring(1, 5, "fBFO USB", 0, 0);
	
	while(!key)
	{
		if(tuningknob > 2)  
		{    
		    f_bfo[0] += n_tstep[cur_tstep];
		    tuningknob = 0;
			show_frequency(f_bfo[0]);
		}	
		
		if(tuningknob < -2)  
		{    
		    f_bfo[0] -= n_tstep[cur_tstep];
			tuningknob = 0;
			show_frequency(f_bfo[0]);
		}	
		
		if(PIND & 0x02) //TX, cause PIND1 is 1
		{
			set_oscillators(1, 14200000, 0);
		    //si5351_set_freq(SYNTH_MS_0, 14200000 + f_bfo[0]);		   
			//si5351_set_freq(SYNTH_MS_1, f_bfo[0]);
		}	
		else
		{
			set_oscillators(0, 14200000, 0);
		    //si5351_set_freq(SYNTH_MS_0, f_bfo[0]);
		    //si5351_set_freq(SYNTH_MS_1, 14200000 + f_bfo[0]);   
		}    
	    key = get_keys();    
	}	
	
	while(get_keys());
		
	//LO FREQ LSB
	key = 0;
	si5351_set_freq(SYNTH_MS_0, f_bfo[1]);	
	show_frequency(f_bfo[1]);
	oled_putstring(1, 5, "fBFO LSB", 0, 0);
	
	while(!key)
	{
		if(tuningknob > 2)  
		{    
		    f_bfo[1] += n_tstep[cur_tstep];
		    tuningknob = 0;
			show_frequency(f_bfo[1]);
		}	
		
		if(tuningknob < -2)  
		{    
		    f_bfo[1] -= n_tstep[cur_tstep];
			tuningknob = 0;
			show_frequency(f_bfo[1]);
		}	
		
		if(PIND & 0x02) //TX, cause PIND1 is 1
		{
			set_oscillators(1, 14200000, 1);
		    //si5351_set_freq(SYNTH_MS_0, 14200000 + f_bfo[1]);		   
			//si5351_set_freq(SYNTH_MS_1, f_bfo[1]);
		}	
		else
		{
			 set_oscillators(0, 14200000, 1);
		     //si5351_set_freq(SYNTH_MS_0, f_bfo[1]);
		     //si5351_set_freq(SYNTH_MS_1, 14200000 + f_bfo[1]);   
		}    
	    key = get_keys();
	}	
			
	while(get_keys());
    oled_putstring(1, 5, "        ", 0, 0);
    return 0;
}

int main(void)
{
	//Keys
	int key = 0, key_old = 0;
	int loopcnt3 = 0;
	
	//Volts measurement
    int adc_v;
    int adc_v_old = 0;
    int v_cnt = 1000;
    double v1;		
        
    //Meter
    int loopcnt1 = 0;
    int sval = 0;
            
    //OPerating frequencies    
    unsigned long freq1 = 14200000;
    unsigned long freq2 = 14200000;
	unsigned long f_scan = 0;
	
	//TX/RX indicator
	int txrx = 0;
	
	//Menu value
	int men = 0;
		
	//INPUT
    PORTC = 0x31;//PC0: Pull-up for key switches with various resistors against GND 
	             //I²C-Bus lines: PC4=SDA, PC5=SCL
	
	PORTD = 0x60;//INPUT: Pullup resistors for PD0 (TX/RX), PD5 and PD6 rotary encoder
					  
	//Watchdog off
	WDTCSR = 0;
	WDTCSR = 0;
    _delay_ms(200);

	//Interrupt definitions for rotary encoder  
	PCMSK2 |= ((1<<PCINT21) | (1<<PCINT22));  //enable encoder pins as interrupt source
	PCICR |= (1<<PCIE2);                      // enable pin change interupts 
	
	//ADC config and ADC init
    ADCSRA = (1<<ADPS0) | (1<<ADPS1) | (1<<ADEN); //Prescaler 64 and ADC on
	get_adc(0); //One dummy conversion
	
	
	freq1 = load_frequency();
	if(freq1 < 14000000 || freq1 > 14350000)
	{
	    freq1 = 14200000;
	}
    freq2 = freq1;
    
	//TWI
	twi_init();
	
	//OLED
	oled_init();
	oled_cls();	

	//Si5351	
	_delay_ms(100);
	si5351_start();
	
    //Frequencies in NORMAL position
    set_oscillators(0, freq1, 0);
    
	//si5351_set_freq(SYNTH_MS_0, f_bfo[sideband]);
	//si5351_set_freq(SYNTH_MS_1, freq1 + f_bfo[sideband]);
		
	show_frequency(freq1);
	show_tstep(cur_tstep, 0);
	show_sideband(sideband, 0);
	draw_meter_scale(0);
    show_store(1, 0, freq1);
    show_store(0, 0, 0);
    show_txrx(0);
    show_split(0);
    show_rcl(0);
    show_scan(0);
    sei();        
    
    for(;;)
    {
        //TUNING		
		if(tuningknob > 2 && !txrx)  
		{    
		    freq1 += n_tstep[cur_tstep];
		    set_oscillators(txrx, freq1, sideband);
			//si5351_set_freq(SYNTH_MS_1, freq1 + f_bfo[sideband]);
			tuningknob = 0;
			show_frequency(freq1);
		}
		
		if(tuningknob < -2 && !txrx)
		{
		    freq1 -= n_tstep[cur_tstep];
		    set_oscillators(txrx, freq1, sideband);
			//si5351_set_freq(SYNTH_MS_1, freq1 + f_bfo[sideband]);
			tuningknob = 0;
			show_frequency(freq1);
		}
				
		//MENU
		if(key_old != key)
		{
			//oled_putstring(0, 20, "    ", 0, 0);
		    //oled_putnumber(0, 20, key, -1, 0, 0);
		    key_old = key;
		    while(get_keys());
		    switch(key)
		    {
				case 1: men = menu();
				        switch(men)
				        {
				            case 4: store_frequency(freq1); //Store QRG
					                show_store(1, 0, freq1);
					                show_store(0, 0, 0);
	                                show_frequency(freq1);				
                                    break;
                                    
                            case 5: if(load_frequency() >= 14000000 && load_frequency() <= 14350000) //Get split freqw
	                                {
	                                    freq2 = load_frequency();
					                }      
					                else
					                {
	                                    freq2 = freq1;
					                }      
					                break;
					                
					        case 6: if(load_frequency() >= 14000000 && load_frequency() <= 14350000) //RCL
					                {
										freq1 = load_frequency(); //Load stored QRG
										set_oscillators(txrx, freq1, sideband);
							            //si5351_set_freq(SYNTH_MS_1, freq1 + f_bfo[sideband]);
	                                    show_frequency(freq1);
	                                }    
	                                break;
	                                
	                        case 7: if(load_frequency() >= 14000000 && load_frequency() <= 14350000)
	                                {      
	                                    show_scan(1);
	                                    f_scan = scan_frequency(load_frequency(), freq1, n_tstep[cur_tstep], sideband);     
	                                    if(get_keys() == 2 && f_scan >= 14000000 && f_scan <= 14350000)
	                                    {
									        freq1 = f_scan;
									    }	
									    show_scan(0);
									    set_oscillators(txrx, freq1, sideband);	
									}
	                                break;
	                                
	                        case 99: set_lo_frequencies();
	                                 set_oscillators(txrx, freq1, sideband);
	                                 show_frequency_cls();
	                                 show_frequency(freq1);
	                                
	                    }            
	        }             
	        show_frequency(freq1);
		}
		
		//VOLTS
		//Voltage divider factor (7.5+2.56k)/2.56 = 3.941176471
		if(v_cnt++ > 5000)
		{
		    v1 = (double) get_adc(2) * 5 / 1024 * 3.94 * 10;
		    adc_v = (int) v1;
   		    if(adc_v != adc_v_old)
		    {
    	        show_voltage(adc_v);
	     		adc_v_old = adc_v;
		    }	
		    v_cnt = 0;
	    }
        
		//After n loops check S-Val resp. PWR value
		if(loopcnt1++ > 20)
		{
			//sval = 390 - get_adc(1); //ADC voltage on ADC1 SVAL
			//oled_putnumber(0, 5, 390 - get_adc(1), -1, 0, 0);
		 	//mval += get_adc(1); //ADC voltage on ADC1 PWR
		 	if(!txrx)
		 	{
				sval = 390 - get_adc(1); //ADC voltage on ADC3 SVAL
		 	    show_meter(sval >> 2); //S-Meter
		 	}
		 	else
		 	{
				sval = get_adc(3); //ADC voltage on ADC3 SVAL
				show_meter(sval); //S-Meter
				
			}    
 		 	loopcnt1 = 0;
		}
		
        //Check if key pressed
        if(loopcnt3++ > 50)
        {
		    key = get_keys();
		    loopcnt3 = 0;
		}    
		
		//TX/RX switching
		if(PIND & 0x02) //TX, cause PIND1 is 1
		{
			if(!txrx)
			{
			    draw_meter_scale(1);	 
			    show_meter(0);
			    show_txrx(1);
			    txrx = 1;
			    
			    //Frequencies in REVERSE position
			    if(split)
			    {
				    show_frequency(freq2);
				    set_oscillators(txrx, freq2, sideband);
					//si5351_set_freq(SYNTH_MS_0, freq2 + f_bfo[sideband]);
				}	 
				else
				{
					set_oscillators(txrx, freq1, sideband);
				    //si5351_set_freq(SYNTH_MS_0, freq1 + f_bfo[sideband]);	
				}
				
				//si5351_set_freq(SYNTH_MS_1, f_bfo[sideband]);
	            
	
			}     
		}	
		
		if(!(PIND & 0x02)) //RX, cause PIND1 is 0
		{
			if(txrx)
			{
				draw_meter_scale(0);
				show_meter(0);
			    show_txrx(0);
			    txrx = 0;
				//Frequencies in NORMAL position
				set_oscillators(txrx, freq1, sideband);
			    //si5351_set_freq(SYNTH_MS_0, f_bfo[sideband]);
  	            //si5351_set_freq(SYNTH_MS_1, freq1 + f_bfo[sideband]);
  	            if(split)
			    {
				    show_frequency(freq1);
				}    
				
			}     
		}
	}
	
	return 0;
}
Advertisements

The „Micro20 III“ – A Simplified Pocket Size SSB Transceiver for 14 MHz

by Peter Rachow (DK7IH)

After having built my first shirt-pocket transceiver about a year ago I occasionally thought of how this or a more or less modified design could be made simpler to save components and therefore limit space as well as reducing the complexity of the whole rig. This was due to the fact that I thought that the ancestor (see link above!) of this project was somehow „overkill“ because I used plenty of stages redundantly that could have been used for receive and transmit operation.

Before we go into the details of this new project, let’s have a look on the new micro transceiver (here operating portable as EA8/DK7IH/QRP from the island of Fuerteventura):

The “Micro20 III (by Peter Rachow, DK7IH)

Cabinet size is about 10 by 4 by 5.5 centimeters which equals to a volume of 220 cubic centimeters (cm³).

Making it simpler without detereorating the performance?

Under the aspect of simplifying the circuit I remembered that I had searched for simple designs of transceiver circuits some years ago intensely. After having revisited some of them on the Internet my attention was caught by the „Antek“-Transceiver that has been published by SP5AHT a while ago. This was an ideal basis for my purpose because I intended to build a „2 mixers+1 filter“ circuit in order to make at least 2 of the 4 mixers that are necessary for a fully functionally SSB transceiver redundant. The central part of SP5AHT’s design matched my requirements in an ideal way:

“ANTEK” Transceiver by SP5AHT

SP5AHT’s circuit uses one mixer (US2 in the schematic) to serve as the receive mixer during rx periods and for the balanced modulator when on transmit. Then the resulting signal is fed through the filter and subsequently processed by mixer 2 (US3). This mixer works as the product detector on receive mode and as transmit mixer when you are on the air. The two oscillators (VFO and LO) are fed to the respective mixer depending on the current operation. This is done by a simple relay connected to the PTT. So when changing from receive to transmit the two oscillators are swapped thus changing the complete function of the circuit.

To get rid of the relay and because I wanted to use the Si5351A clock oscillator chip, my idea was making two of the 3 oscillators present in the clock chip act as LO and VFO. By software, when switching from rx to tx, these oscillators’ frequencies are simply swapped. The microcontroller driving the Si5351A reads the PTT and when pressed to talk the frequencies present on CLK0 and CLK1 are put out reversely. Thus no hardware switching is required.

In addition the audio amps in the end of the receiver chain are powered off. Instead of this the rf power amp of the transmitter section is connected to +12V DC as well as the microphone amp. The antenna relay disconnects the receiver front end from the antenna line and connects the antenna to the LPF that is installed after the rf power transformer of the rf power amp final stage.

By this the whole transceiver is constructed much simpler and lots of circuitry has been removed from the rig.

The block diagram

Micro20-III SSB QRP transceiver by DK7IH – Block diagram

Circuit explanation: During receive periods the signal is fed into the antenna jack whose line is switched by the antenna relay and fed to the first band pass filter for 14MHz when listening to the band. Afterwards it is amplified by a dual-gate MOSFET transistor that is connected to the AGC line that reduces gain when strong signals are detected. The next stage is mixer 1 where a VFO signal of about 24 MHz comes from CLK0 of the Si5351A module.

The result of this mixing process is the IF of about 9 or 10.7 MHz or whatever frequency you are about to use depending on the filter you have installed. This signal is amplified by another dual-gate MOSFET. On receive mode this stage is also under AGC control. When transmitting it is powered to full gain of about 18dB applying +12V via a 2:1 voltage divider consisting of 2 resistors with 82k each.

Next step is mixer 2 where the IF signal is mixed with a 9 resp. 10.7 MHz LO signal (receive mode serving a product detector) or with the VFO signal in mixer 2 serving as tx mixer.

On receive two audio stages (a preamp with LPF in advance and a power amp) amplify the audio signal to a level that can be fed into an 8 Ohm loudspeaker.

On transmit a BPF eliminates the unwanted mixing products and a three stage rf power amplifier lifts the signal to a power level of 3 watts peak power.

The Main RF Section

This central section of the rig consists of the two mixers mentioned before, a commercially made (in my case) 10.695 MHz filter stripped from an old CB radio, some amplifier stages and so on. The circuit is the following one:

The “Micro20-III” SSB QRP transceiver by DK7IH – Main RF board

Starting from left side top there is the first receiver amplifier stage using a dual-gate MOSFET. The use of a MOSFET transistor ensures that the noise figure and sensitivity of the whole receive improve very much. The stage is comnnected to the AGC chain. The dc voltage applied by the AGC section varies in the range from 0 to 6 V DC. In addition it can be set by hand by turning a front panel mounted potentiometer that alters the DC voltage in the range from 0 volts to max. volts from the ADC section.

The input is a single tuned circuit using 4 (antenna side) by 16 turns (MOSFET gate side) on a TOKO 5,5 mm coil former. Parallel capacity is 47 pF. In drain line of the transistor the same filter is used coupling the signal to the first mixer (NE612). Note that the second filter is reversed (secondary in drain line) to avoid self-oscillation of the preamp stage.

This mixer’s signal is fed with 2 different input signals (antenna or microphone) and with 2 different oscillator frequencies: about 24 MHz on receive mode or 10.695 +/- 1.5 kHz depending on sideband used when on transmit. The resulting interfrequency signal is fed into an SSB filter which is terminated by 2 resistors of 1k each side to ensure proper impedance matching.

Next stage is the interfrequency amplifier which is the same circuit like the receiver’s preamplifier. This one is also connected to the AGC’s DC line. On transmit mode with no AF signal on the AGC present this stage runs on full gain.

The chain is completed by the second mixer serving as product detector on receive and as tx mixer when going “on the air”.

The audio and AGC section

The “Micro20-III” – A small 20 meter QRP SSB transceiver by DK7IH – Audio amp and AGC

This section also is no “rocket science”. A simple preamplifier using a bipolar transistor (BC846) in common emitter mode, a low pass filter (R=1k and C=0.22uF) and an LM 386 amplify the resulting af sig to an adequate volume to listen to the sound even in an environment that is not 100% noise free.

The RF power module

This is a circuit I have built several times and it’s capable of delivering up to 5 watts of rf power. In this transmitter I’m not driving it beyond 3 watts which is suffice to establish connections on the 20 meter band worldwide a well performing antenna provided.

The “Micro20-III” – A small 20 meter QRP SSB transceiver by DK7IH – RF Power Amp

Emitter degeneration and negative feedback are present in preamp and driver stage to ensure maximum linearity. Both stages are operated in class A mode. The final stage works as a push-pull stage using class AB. Push-pull mode eliminates even order harmonics by circuit feature. A heatsink is mandatory for the final stage (the mounting frame in my case) and at least recommended for the driver. The values for the broadband transformers are stated in the schematic above.

The VFO module

This one is equipped with the clock oscillator chip Si5351A by Silicon Labs. I use it mounted to the well known Adafruit breakout board that can handle 5 volts even if the chip is designed for 3.3V. So, this board is compatible to standard microcontrollers like the ATmega168 that is applied in my transceiver. The display is the 1306 chipset based OLED that is also designed for 5 volts supply voltage. Both, the Si5351 and the OLED are designed for I²C-interface which is called “Two Wire Interface” (TWI) in Atmel’s language. The major advantage this interface has got is that only 2 control lines are required, one of them clock (SCL) and the other data line (SDA) to transfer data to the respective units. Basically you need two pull up resistors to tie these lines to +5VDD but I use the internal pull-up resistors in the Atmega168’s ports that do the job well.

The “Micro20-III” – A small 20 meter QRP SSB transceiver by DK7IH – VFO with Si5351, ATmega168 and OLED 1306

Problem to be mentioned: When testing the early version of the receiver I found the OLED to be very noisy. After a brief research I realized that the signals that were audible in the receiver traveled on the VDD line. Thus I inserted an 82R series resistor and a set of blocking capacitors in the place which made the noise fully disappear.

Practical setup

The main board of the transceiver is made of a 5 by 7 cm breadboard with double-sided soldering pads each connected by a small tubing electrically connecting the both sides of the pad . This is a big advantage when you solder SMD components because you can setup the circuit on both sides of the board and save a lot of space. NExt is that it is nearly impossible to dissolder the pads even if you are resoldering the spots many times. The reason: The soldering pads are rivets anchored on both sides of the carrier of the board.

The components that aren’t available in SMT are standard through-hole but there are only a few like the SSB filter or some old 40673s I used instead of e. g. BF991. Coils for low power are wound on TOKO 5.5 mm coil formers. Power rf transformers are connected to soldering nails in the board. The inside view plus a centimeter scale:

The “Micro20-III” – A small 20 meter QRP SSB transceiver by DK7IH – Inside view from top

On the left you can see the front panel with the controls, behind the panel the OLED and then the main rf board. The Si5351 is mounted vertically on top left from the AGC board. Underneath there is the receiver’s front end (hidden by the red power supply cable). On the right I sited the power transmitter, at the bottom there is the relay for tx/rx switching. All is built into a 9 * 5.5 * 4 cm Aluminum frame.

Being “on air” with the rig

Operating is really fun with this micro transceiver. Since the finishing of the transceiver 4 weeks ago I was on air daily. Here are some regions of the world I could establish successful contacts with. Antenna is a Delta Loop fed in on upper corner about 12 meters above ground.

Plans for the future

Several options may be my next project: First I got plans are to expand this rig to a multibander (due to Si5351’s capabilites of generating max. 160MHz signals), build a PA of about 30 watts max. power (with 2 transistors 2SC1969 in push-pull mode) or to rebuild the transceiver for 17 meter band and hoping that conditions will be better the next couple of years. Let’s see what the real deal will be! 😉

73 de Peter (DK7IH)

 

First comparison between AD9xxx DDS and Si5351 clock chip

In the meanwhile I changed the VFO section my current project, which is a “high performance” TRX for 40 meters, from an AD9834 module to the Adafruit Si5351 breakout board.

My first impression (just from the listerning to the receiver) are as follows:

  • The number of audible spurs has decreased significantly with the “Si”. There are some, but very much less than with the AD9834 DDS module. They completely disappear when the receiver is connected to the antenna because band noise is stronger in this case.
  • The number of strong broadcast stations that I receive is zero. The receiver did not have many problems at all before with the AD9834 because the front end consists of a three-pole 7MHz filter, a Dual Gate MOSFET serving as a regulated rf preamplifier and a doubly balanced diode ring mixer. But some broadcasters were discernable as images from the spurs the AD9834 DDS generated. This problem has been solved completely with the “Si”.

Another advantage of the Si5351 is that it has more output channels that you can use. Or to say more exactly: The “A” version of the chip has 3 outputs (CLK0, CLK1, and CLK2). So, you can easily implement split mode or RIT functionality  just by e. g. connecting CLK0 to the RX mixer and CLK1 to the TX mixer and adjusting the frequencies as needed in the respective situation.You also don’t have to decouple the rf lines coming from the module because they are decoupled internally in the Si5351 chip.

The frequency span of the Si5351 is much bigger than that of the ADs. I could get out up to 120 MHz with my simple code. But minimum frequency is 2.5 kHz according to the datasheet. With the ADs you can get near DC in this case. The waveform of the “Si” on the other hand turns to something like a sinewave when you go above 80 MHz but that doesn’t matter that much.

With the “bigger” ADs (AD9951 etc.) you can go up to 120 Mhz provided you have a 400MHz clock generator. But you need 2 or 3 different voltages (1.8, 3.3 and maybe 5 Volts if you microcontroller runs on 5V). The Si5351 only needs 3.3 V (or 5V if you use the Adafruit breakout board or a similar one)

But one shortcoming of the Si5351 should also be mentioned: The software coding for the Si5351 is a little bit more complex than the AD9xxx. I worked two days because I wanted to write my own functions based on the data sheet which turned out a little bit complicated.

For the AD9xxx when economically coded you only need 2 to 3 dozens of lines of C source code. When checking thru my “minimal code” you will find much more programming effort for the “Si”. But this is not a problem at all.

Conclusion: The Si5351 is a great alternative to the AD9xxx chips made by AD. I will do further experiments and keep you informed.

73 de Peter

A simple software to control the Si5351A clock generator chip

A fellow radio amateur who visited my website gave me a hint for a very cheap module that is capable of generating rf sginals up to 200 MHz. It is the well-known Si5351A clock generator chip made by Silicon Labs (datasheet).It is available by ADAFRUIT mounted to a breakout board using 0.1″ conventional spacing. The chip itself is very small, so by using the ADAFRUIT stuff you don’t have to bother soldering SMDs to a PCB. (Link to ADAFRUIT). The chip is designed for 3.3V supply, on the Adafruit board you can find circuits to make this chip usable for 5V supply and 5V control lines. So it is compatible to standard 5V digital equipment.

adafruit_products_2045iso_orig

(Picture courtesy ADAFRUIT)

In contrast to the DDS chips I have been using before this one produces square waves. But especially for mixer purposes in a radio this can be an advantage because mixers generally are well controlled by square waves. And due to the mandatory post-mixer filtering circuitry harmonics are easily suppressed.

The Si5351 generator is intended to replace clock sources of all kinds, build PLL generators etc. It is fully programmable via I2C bus, in ATMega language called “TWI” (two wire interface).

My software does not use the Arduino code or other libraries, all TWI functions are written into the file to make understanding more easy without the neccessity to watch different files.

Basic guidelines for programming

Programming the Si5351 is a little bit more complicated than to set the frequency of the AD9xxxx DDS chips by Analog Devices that are well mentioned on my website. The programming of the PLL(s) and the synthesizer(s) is described in AN619 of SiLabs (Link). There are 2 steps to get the desired frequency out of the module.

Step 1: Set the PLL to a basic frequency (in my code to f=900 MHz)

Step 2: Divide this frequency using a “MultiSynth” divider to the desired output frequency using a set of equations given in AN619 and send this to one of the outputs (CLK0 to CLK2 with the Si5351A).

For handling step 1 you can see the function void si5351_start(void) in the code. Step 2 is done by the function void si5351_set_freq(int synth, unsigned long freq). Both functions look similar due to the fact that basic arithmetics do not differ very much. They got in common that you first have to calculate a set of integer values and subsequently write them into a larger number of registers of the Si5351. To understand this more easily I have written the register numbers into the code. It is highly recommended to watch the register table in AN619 to see the corresponding memory locations.

The software is very simple. You can generate one frequency that will be transferred to CLK0 output on the PCB. Watch the code:

/*****************************************************************/
/*             RF generator with Si5153 and ATMega8              */
/*  ************************************************************ */
/*  Mikrocontroller:  ATMEL AVR ATmega8, 8 MHz                   */
/*                                                               */
/*  Compiler:         GCC (GNU AVR C-Compiler)                   */
/*  Author:           Peter Rachow (DK7IH)                       */
/*  Last Change:      2017-FEB-23                                */
/*****************************************************************/
//Important:
//This is an absolute minimum software to generate a 10MHz signal with
//an ATMega8 and the SI5351 chip. Only one CLK0 and CLK1 are used 
//to supply rf to RX and TX module seperately. 

//I have tested this software with my RIGOL 100Mhz scope. Up to this
//frequency the Si5331 produced output.

//The software is more for educational purposes but can be modfied 
//to get more stuff out of the chip.
//
//73 de Peter (DK7IH)

#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>

/////////////////////
//Defines for Si5351
/////////////////////
#define SI5351_ADDRESS 0xC0 // 0b11000000 for my module. Others may vary! The 0x60 did NOT work with my module!

//Set of Si5351A register addresses
#define CLK_ENABLE_CONTROL       3
#define PLLX_SRC				15
#define CLK0_CONTROL            16 
#define CLK1_CONTROL            17
#define CLK2_CONTROL            18
#define SYNTH_PLL_A             26
#define SYNTH_PLL_B             34
#define SYNTH_MS_0              42
#define SYNTH_MS_1              50
#define SYNTH_MS_2              58
#define PLL_RESET              177
#define XTAL_LOAD_CAP          183

//The unavoidable functional stuff
int main(void);
void wait_ms(int);

//  TWI Declarations
void twi_init(void);
void twi_start(void);
void twi_stop(void);
void twi_write(uint8_t u8data);
uint8_t twi_get_status(void);

//  SI5351 Declarations
void si5351_write(int, int);
void si5351_start(void);
void si5351_set_freq(int, unsigned long);

/////////////////////
//
//   TWI-Functions
//
/////////////////////
void twi_init(void)
{
    //set SCL to 400kHz
    TWSR = 0x00;
    TWBR = 0x0C;
	
    //enable TWI
    TWCR = (1<<TWEN);
}

//Send start signal
void twi_start(void)
{
    TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
    while ((TWCR & (1<<TWINT)) == 0);
}

//send stop signal
void twi_stop(void)
{
    TWCR = (1<<TWINT)|(1<<TWSTO)|(1<<TWEN);
}

void twi_write(uint8_t u8data)
{
	TWDR = u8data;
    TWCR = (1<<TWINT)|(1<<TWEN);
    while ((TWCR & (1<<TWINT)) == 0);
}

////////////////////////////////
//
// Si5351A commands
//
///////////////////////////////
void si5351_write(int reg_addr, int reg_value)
{
   twi_start();
   twi_write(SI5351_ADDRESS);
   twi_write(reg_addr);
   twi_write(reg_value);
   twi_stop();
} 

// Set PLLs (VCOs) to internal clock rate of 900 MHz 
// Equation fVCO = fXTAL * (a+b/c) (=> AN619 p. 3 
void si5351_start(void) 
{ 
  unsigned long a, b, c; 
  unsigned long p1, p2, p3; 
   
  // Init clock chip 
  si5351_write(XTAL_LOAD_CAP, 0xD2);      // Set crystal load capacitor to 10pF (default),  
                                          // for bits 5:0 see also AN619 p. 60 
  si5351_write(CLK_ENABLE_CONTROL, 0x00); // Enable all outputs 
  si5351_write(CLK0_CONTROL, 0x0F);       // Set PLLA to CLK0, 8 mA output 
  si5351_write(CLK1_CONTROL, 0x2F);       // Set PLLB to CLK1, 8 mA output 
  si5351_write(CLK2_CONTROL, 0x2F);       // Set PLLB to CLK2, 8 mA output 
  si5351_write(PLL_RESET, 0xA0);          // Reset PLLA and PLLB 
 
  // Set VCOs of PLLA and PLLB to 900 MHz 
  a = 36;           // Division factor 900/25 MHz 
  b = 0;            // Numerator, sets b/c=0 
  c = 1048757;      //Max. resolution, but irrelevant in this case (b=0) 
 
  //Formula for splitting up the numbers to register data, see AN619 
  p1 = 128 * a + (unsigned long) (128 * b / c) - 512; 
  p2 = 128 * b - c * (unsigned long) (128 * b / c); 
  p3  = c; 
 
  //Write data to registers PLLA and PLLB so that both VCOs are set to 900MHz intermal freq 
  si5351_write(SYNTH_PLL_A, 0xFF); 
  si5351_write(SYNTH_PLL_A + 1, 0xFF); 
  si5351_write(SYNTH_PLL_A + 2, (p1 & 0x00030000) >> 16); 
  si5351_write(SYNTH_PLL_A + 3, (p1 & 0x0000FF00) >> 8); 
  si5351_write(SYNTH_PLL_A + 4, (p1 & 0x000000FF)); 
  si5351_write(SYNTH_PLL_A + 5, 0xF0 | ((p2 & 0x000F0000) >> 16)); 
  si5351_write(SYNTH_PLL_A + 6, (p2 & 0x0000FF00) >> 8); 
  si5351_write(SYNTH_PLL_A + 7, (p2 & 0x000000FF)); 
 
  si5351_write(SYNTH_PLL_B, 0xFF); 
  si5351_write(SYNTH_PLL_B + 1, 0xFF); 
  si5351_write(SYNTH_PLL_B + 2, (p1 & 0x00030000) >> 16); 
  si5351_write(SYNTH_PLL_B + 3, (p1 & 0x0000FF00) >> 8); 
  si5351_write(SYNTH_PLL_B + 4, (p1 & 0x000000FF)); 
  si5351_write(SYNTH_PLL_B + 5, 0xF0 | ((p2 & 0x000F0000) >> 16)); 
  si5351_write(SYNTH_PLL_B + 6, (p2 & 0x0000FF00) >> 8); 
  si5351_write(SYNTH_PLL_B + 7, (p2 & 0x000000FF)); 
 
} 
 
void si5351_set_freq(int synth, unsigned long freq) 
{ 
   
  unsigned long  a, b, c = 1048575; 
  unsigned long f_xtal = 25000000; 
  double fdiv = (double) (f_xtal * 36) / freq; //division factor fvco/freq (will be integer part of a+b/c) 
  double rm; //remainder 
  unsigned long p1, p2, p3; 
   
  a = (unsigned long) fdiv; 
  rm = fdiv - a;  //(equiv. b/c) 
  b = rm * c; 
  p1  = 128 * a + (unsigned long) (128 * b / c) - 512; 
  p2 = 128 * b - c * (unsigned long) (128 * b / c); 
  p3 = c; 
   
  //Write data to multisynth registers of synth n 
  si5351_write(synth, 0xFF);      //1048757 MSB 
  si5351_write(synth + 1, 0xFF);  //1048757 LSB 
  si5351_write(synth + 2, (p1 & 0x00030000) >> 16); 
  si5351_write(synth + 3, (p1 & 0x0000FF00) >> 8); 
  si5351_write(synth + 4, (p1 & 0x000000FF)); 
  si5351_write(synth + 5, 0xF0 | ((p2 & 0x000F0000) >> 16)); 
  si5351_write(synth + 6, (p2 & 0x0000FF00) >> 8); 
  si5351_write(synth + 7, (p2 & 0x000000FF)); 
} 
/////////////////////////////////////////////
//              M  I  S  C  
/////////////////////////////////////////////
//Substitute defective _delay_ms() function in delay.h
void wait_ms(int ms)
{
    int t1, t2;
    int dtime = (int) 137 * 8;

    for(t1 = 0; t1 < ms; t1++)
    {
        for(t2 = 0; t2 < dtime; t2++)
        {
            asm volatile ("nop" ::);
        }
    }        
}

int main(void)
{
	unsigned long t1, freq = 10000000;
	PORTC = 0x30;//I²C-Bus lines: PC4=SDA, PC5=SCL 
				
	twi_init();
	wait_ms(100);
	si5351_start();
	wait_ms(100);
	
	si5351_set_freq(SYNTH_MS_0, freq);
    wait_ms(5000);
    
    for(;;)
    {
		//Increase frequency in steps of 1kHz 
	    for(t1 = 10000000; t1 < 10090000; t1+=1000)
	    {
			si5351_set_freq(SYNTH_MS_0, t1);
			wait_ms(1000);
		}	
	}	
    return 0;
}

I found lots of code for the Si5351 on the web, most of it much too long for my ideas. I reused some of the better parts and added some corrections and modification to fit my needs.

Some authors join the two functions (PLL definition and multisynth definitions) in the set_freq() function. I tried to avoid this because the code will be faster if you only set the PLLs once and afterwards just modify the frequency. Another idea that should be avoided is to switch off the PLLs during reprogramming the frequency. Unpleasant short-term absence of receiving signal is an outcome of the switch-off strategy. I leave the Pll running the whole time.

Thanks again for watching my amateur radio blog!

73 de Peter

The Micro QRP Transceiver – A pocketful of radio in SMT

Recently I thought about how small a fully functionable ham radio could be. Or to say in other words: Should it be possible to build a QRP SSB transcevier that fits into a shirt pocket?

When searching the web for very small amateur radio SSB transceivers I found Pete Juliano’s (N8QW) “Shirt pocket transceiver“. This is a really neat transceiver with 2 Watts of output as Pete says.

My goal was to achieve also 2 Watts of output but keeping the rig even smaller. This was, as I quickly recognized, only possible by replacing conventional through-hole construction by using SMD components to a wide extent. Other things that had to be done were to get an LCD with as little physical expansion as possible and to find small potentiometers for the front panel, also as small as possible.

As an LCD due to the prerequisites mentioned before I use an OLED with 0.96″ diagonal size. This display is available in SPI or I²C/TWI interface technology. Due to the fact that I²C/TWI only uses 2 control lines (which would save me some effort when wiring this) I chose this version. The rest of the transceiver is standard QRP stuff:

  • AD9835 as DDS driven by an Arduino Pro Mini board with ATMega328 uC.
  • NE602s as SSB generator, TX mixer, RX mixer and product detector
  • MC1350 as IF amp
  • Dual-Gate-MOSFET as RX preamp

The only one of the usual suspects missing in this project is the LM386 audio amplifier. This has been replaced by a push-pull audio amp with a PNP/NPN pair of bipolar transistors.

I revived the construction of my 20m-handheld transceiver just to shrink it to a size as small as possible. Underneath you can watch the final state of this project “micro transceiver”. First the outside view. It easily fits, as you can see, in one hand.

QRP micro transceiver by DK7IH - 20 meters 2 Watts SSB ins SMDT/SMD technology - outside view
QRP micro transceiver by DK7IH – 20 meters 2 Watts SSB ins SMDT/SMD technology – outside view

Now for the inside:

QRP micro transceiver by DK7IH - 20 meters 2 Watts SSB ins SMDT/SMD technology - inside view
QRP micro transceiver by DK7IH – 20 meters 2 Watts SSB ins SMDT/SMD technology – inside view

Explanation: Starting in the left upper corner you can see the AD9835 DDS mounted to a breakout board.

Going clockwise next is the receive mixer and the front end with a 40673 dual gate MOSFET transistor. On the right side there is the transmitter. First the final amplifier (push-pull technology), followed by driver and preamp. On the right bottom there is the transmitter’s BPF. Next is the ladder filter for the tx section, “north” of that the transmit mixer, ladderfilter, once again “north” you can see the balanced modulator and the mic amp fed by a shielded cable leading to the front panel.

In the center there is the latter part of the receiver, MC1350 if amp, product detector and audio amps (pre and final stage) sited in the left bottom corner.

The front panel holds the microcontroller (an Arduino Pro mini used as a native AVR without Arduino software), the 0.96″ OLED and the controls.

The transceiver interior section viewed from front:

Micro QRP SSB transceiver for 20m 14MHz by Peter (DK7IH) - front view inside
Micro QRP SSB transceiver for 20m 14MHz by Peter (DK7IH) – front view inside

And here is the full circuit of my micro transceiver:

Micro QRP SSB transceiver for the 20 meter band (14MHz) by Peter, DK7IH
Micro QRP SSB transceiver for the 20 meter band (14MHz) by Peter, DK7IH

To watch the schematic in full size click here.

The transmitter puts out 2 to 2.5 watts SSB. Applying a two-tone signal shows the following output:

Signal of my 20 meter 14 Mhz micro SSB transceiver (about 2.5 watts output)
Two-tone-signal of my 20 meter 14 Mhz micro SSB transceiver (fully driven, about 2.5 watts output)

 

Excursus: What about using SMDs on Veroboards?

SMDs are not only for printed circuit boards. You can also use them on the standard 2.54mm (0.1″) pitch veroboards.. When you would like to start a similar project, I strongly recommend, in addition to the standard electronic toolcase, the following working material and equipment:

  • A high quality temperature stabilized soldering station (I use a Weller WHS 40D) with a 2mm solder tip (wedge-shaped),
  • Small pairs of tweezers of various sizes,
  • A surgical magnifier (I personally use the “RidoMED” by German manufacturer Eschenbach (Link) which sells for about 320,- Euros)
  • 2 or 3 desk lights coming from various angles to you work area.

Where can I get that SMD stuff at a reasonable price?

SMD components are best bought in assortments from the well known electronic warehouses on the web. I bought for example  abt. 4000 resistors more than 50 different values) from Chinese vendors via ebay for not more than 5 USD. The same is valid for capacitors. Transistors are also on stock, for example the BC837, BC 846 and other types. ICs in SMD case that can also be purchased are the NE602,612 etc., LM386, MC1350. So, after some weeks a reliable base of SMD componenst is right at hand. For any conceivable QRP project there is enough material available to start.

Soldering SMD components

Standard Veroboards with 0.1″ spacing (2.54mm) can be used with 0603 and 0805 and other SMD parts easily. They well fit into the spaces between the dots on the veroboard.

My method of using SMDs on the veroboard: First put a little bit of solder tin to one of the dots where you want to mount the part. Fix the part with one leg/side by soldering it to the board. Then (if neccessary) readjust the component with the pair of tweezers so that it fits accurately to the board. Keep an eye on the fact that it should be sited plain on the board. If neccessary press it down with a pair of tweezers carefully while heating up the soldering point. Finally check the component from various perspectives!

After having got the correct position of the component, solder the remaining leads and control your work with a magnifying glass, a jeweller’s loupe or the surgical magnifier.

Desoldering SMD components

Desoldering is, to my opinion, easier than with through-hole components:

Parts with 2 soldering areas: Heat the leads of the component from both sides quickly by changing the side with your soldering iron. Soldering tin on both sides must be liquid. Keep the part with the pair of tweezers using your other hand. After some seconds the part will move, in most cases it will stick to the solder tip. Don’t reuse this part!  It might have become damaged by thermal stress in case of excessive heat appliance when desoldering process takes longer or by mechanical stress.

Parts with more leads (transistors etc.) : I heat  one edge of the part to desolder 2 pins. Simultanously I grap the part with the pair of tweezers and bend the leads up when the solder has melted. Then I desolder the rest of the part.

So, I hope I could give you another inpiration for building a small compact transceiver using SMDs. 73s and thanks for watching!

Peter (DK7IH)

 

 

 

 

Cheap and dirty measurement of filter response curves

When cleaning up my shack I found one of those very cheap China made DDS modules containing an AD9850 synthesizer. “Let’s do something with it!” was the decision. I connected an ATMega328 to it and wrote a very short piece of software to sweep the frequency between two edging values. This signal is sent thru a filter to get the respective response curve without taking great effort:

Measuring filter response curves with microcontroller and China made DDS modul (AD9850)
Measuring filter response curves with microcontroller and China made DDS modul (AD9850)

I connected the output of the DDS to a ladder filter I wanted to test. On my spectrum analyzer (oldie but goldie HP8558B) I got the expected outcome. But then I started thinking of those amateurs who are not the proud owners of such an instrument. So I connected the filter output to my RIGOL DS1054Z digitial scope. After some tries I was able to synchronize the DDS’ sweep time with the horizontal sweep of the scope an got the filter curve on the screen:

cheap-filter-measurement-2

When you put horizontal sweep rate to a very low value you will finaly see the filter response curve. This is not very exact due to the lack of frequency readout on the scope’s x axis but it is OK if you want to check the flatness of a filter. And it is quickly done.

But due to my fascination for exact things I will trigger the scope by the microcontroller so that I can deduce a unit of khz per cm on my scope screen.

73 and thanks for watching my blog!

Peter

The software:

/*****************************************************************/
 /* Frequency sweeper with ATMega8 + AD9850 */
 /* ************************************************************ */
 /* Mikrocontroller: ATMEL AVR ATmega328p, 8 MHz */
 /* */
 /* Compiler: GCC (GNU AVR C-Compiler) */
 /* Autor: Peter Rachow */
 /* Letzte Aenderung: 16.11.2016 */
 /*****************************************************************/


#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

int main(void);

/*******************/
 // SPI
 /*******************/
 //Port usage
 //FQ_UD: PB0 (1) blue
 //SDATA: PB1 (2) green
 //W_CLK: PB2 (4) white
 //RESET AD9850 PD7 yellow

void set_frequency_ad9850(unsigned long);

//************
 // SPI
 //************

//Set AD9850 to desired frequency
 void set_frequency_ad9850(unsigned long fx)
 {
 unsigned long clk = 125000000;
 unsigned long x = 1;
 int t1;

double fword0;
 unsigned long fword1;

fword0 = (double) fx / clk * 4294967296;
 fword1 = (unsigned long) fword0;

//Send 32 frequency bits + 8 additional bits to DDS
 //Start sequence
 PORTB &= ~(1); //FQ_UD lo => Bit PD0 = 0

for(t1 = 0; t1 < 32; t1++)
 {
 if(fword1 & x << t1)
 {
 PORTB |= 2; //SDATA Bit PB1 setzen
 }
 else
 {
 PORTB &= ~(2); //SDATA Bit PB1 löschen
 }

//W_CLK hi
 PORTB |= 4; //Bit PB2 setzen

//W_CLK lo
 PORTB &= ~(4); //Bit PB2 löschen
 }

//W32...W39 all bits are 0!
 PORTB &= ~(2); //SDATA Bit PB1 löschen
 for(t1 = 0; t1 Bit PD0 = 1

}

int main()
 {

unsigned long fx0 = 10000000;
 int swing_f = 10000;
 int delta_f = 5;
 unsigned long fx1 = fx0 - swing_f;
 unsigned long fx2 = fx0 + swing_f;
 unsigned long f;

DDRB = 0xFF; //SPI (PB0..PB2)
 DDRD = 128;

PORTD |= 128; //Bit PD7 set
 _delay_ms(1000); //wait for > 20ns i. e. 1ms minimum time with _delay_s()
 PORTD &= ~(128); //Bit PD7 erase

for(;;)
 {
 for(f = fx1; f 20ns i. e. 1ms minimum time with _delay_s()
 PORTD &= ~(128); //Bit PD7 erase

}

return 0;
 }

A very compact SSB transceiver for 40 Meters with 50 watts of output power (Product detector, AF, AGC)

The demodulator section of the transceiver’s receiver starts with the product detector, which is made of another SA602. To get more audio volume a preamplifier has been added before the LM386 follows.

Homemade SSB amateur radio transceiver 40 meters (SSB demodulator, AF, AGC section)
Homemade SSB amateur radio transceiver 40 meters (SSB demodulator, AF, AGC section)

The AGC section hast got 2 crucial components: One resistor (this case 100k) and an electrolytic capacitor (in this case 100uF): They determine the time ramp for the AGC regulation curve. This means they define the response and decay time for the AGC and thus should be made easily changable for example by putting them into socket strips.

Hint: In certain cases it can be useful to add a potentiometer to give you control on the audio input of the AGC preamplifier.

Thanks for watching!

73 de Peter