Frequency generator based on the LTC6946

This project came up since I was in need of a frequency generator to generate frequencies in the order of 300 MHz – 1 GHz.
I was experimenting with ON and OFF keying (OOK) in the 430 MHz band and in general wanted to have a frequency generator that can generate frequencies in the 70 cm amateur radio band.

My decision fall on the LTC6946-1 [1] from Linear Technology a Ultralow Noise 0.37 GHz to 3.740 GHz Integer N Synthesizer with Integrated VCO. The main reason is that I had one laying around in my lab πŸ™‚

The nice thing with this chip is that it does not need too many components around it. Some capacitors and inductors for the power and the capacitors and a resistor for the loop-filter.
The programming is done easily via the SPI bus.

There exist similar chips from Analog devices like for example the ADF4360-X.

I wanted to build the generator as a stand alone solution, not only controllable via a computer. Therefore I placed an ATMEL MEGA 8 bit MCU on the board together with 6 push buttons and a connector for a LCD display.

The MCU runs with 5 V logic level the LTC6946 has 3.3V logic levels, so I put in between the SPI bus a logic level shifter based on the BSS138 [2] MOSFET.

The board was designed with KiCAD [3].

LTC6946_PCB LTC6946_3dPCB

The schematic looks the following:

LTC6946_Schematics

the whole schematic can be viewes as a PDF: LTC6946 Schematics.

The finished PCB:
LTC6946_PCB

Loop filter considerations

The synthesizer uses a 19.68 MHz reference clock. How to calculate the values for the loop filter is described in detail in the datasheet.

Starting with the reference frequency f_{REF}=19.68 MHz we can calculate the PFD frequency.

f_{PDF}=\frac{f_{REF}}{R}=\frac{19.68 \text{MHz}}{20}=984000 \text{kHz}

In the datasheet it is written that the loop filter BW should be at least 10x smaller than the PFD frequency!
For the calculations a VCO frequency of 3 GHz is assumed. This requires a N of around 3050.

f_{VCO}=\frac{f_{REF}\cdot N}{R}

With the output divider (OD) of 6 this gives 500 MHz.

From the datasheet K_{VCO}=4.7-7.2 \%\;\text{Hz/V} which can be calculated to:

K_{VCO}=3\text{GHz}\cdot\sqrt{0.047\cdot0.032}=174.5 \text{MHz}

Together with the charge pump current of I_{CP}=11.2 \text{mA} the resistor R_Z can be calculated:
R_Z=\frac{2\cdot \pi \cdot \text{BW} \cdot N}{I_{CP}\cdot K_{VCO}}=392\Omega

The bandwidth (BW) was chosen to be 40 kHz.

C_L and C_I can be calculated according to the formulas:
C_I=\frac{3.5}{2\cdot\pi\text{BW}\cdot R_Z}
and
C_P=\frac{1}{7\cdot\pi\text{BW}\cdot R_Z}

I ended up for my loop filter with the following values:
R_Z=450\Omega, C_P=2.7\text{nF} and C_I=28.8\text{nF} these were some values close to that what the calculation gives and I found this particular values on the schematics of an evaluation board.

Control of the generator

In order to be able to control the LTC6946 the ATMEL MEGA MCU is connected with push buttons and the LCD. Some code was written to control the whole unit.


#include <SPI.h>
#include <LiquidCrystal.h>

const int chipSelectPin = 10;

const int LCD_RW=3;//PD3;
const int LCD_RS=4;//PD4;
const int LCD_E=5;//PD5;
const int LCD_D4=6;//PD6;
const int LCD_D5=7;//PD7;
const int LCD_D6=8;//PB0;
const int LCD_D7=9;//PB1;


const int PushButtonMenu=2;//B1
const int PushButtonSelect=A5;
const int PushButtonLeft=A4;
const int PushButtonUp=A3;
const int PushButtonRight=A2;
const int PushButtonDown=A1;


int LastButtonState=LOW;
long ButtonPushCount=0;


LiquidCrystal lcd(LCD_RS, LCD_RW, LCD_E, LCD_D4, LCD_D5, LCD_D6, LCD_D7);

unsigned long int GlobalFrequency=400000000;
const unsigned int R=41;//the low 8 bits of the R div, try R=41
const unsigned long f_PFD=19680000/R;//Hz

unsigned int FreqIncr=0;//factor by what to incrase freq 0 = pfd, 1=1MHz, 2=10 MHz, 3=100Mhz


void setup() {
  //init LCD
  lcd.begin(16, 2);
  // Print a message to the LCD.
  lcd.print("Initializing ...");
   
  // put your setup code here, to run once:
  Serial.begin(9600);

  // start the SPI library:
  SPI.begin();
  pinMode(chipSelectPin, OUTPUT);


}

boolean ButtonIsPushed(int ButtonPin){
  if(digitalRead(ButtonPin)==HIGH){
    if(LastButtonState==LOW){//Check if we released the button in the meantime or push for longer time
      LastButtonState=HIGH;
      delay(100);
      return true;
    }
    else{
      ButtonPushCount++;//count how long the button is pressed
      delay(100);
      if(ButtonPushCount>20000){
        return true;
      }  
      return false; 
    }
  }
  else{//Button is not pressed
    LastButtonState=LOW;
    ButtonPushCount=0;
    return false;
  }
}

bool checkButtonAction(){
  //we have a pfd freqyency f_PFD
  //depending on the OD (output divioder we have different step values
  //depending on teh frequeny range

  unsigned long NewValue=0;
  
   if(ButtonIsPushed(PushButtonUp)){//up count
    if(GlobalFrequency<=623000000)NewValue=GlobalFrequency+80000;
    else if(GlobalFrequency<=748000000)NewValue=GlobalFrequency+96000;
    else if(GlobalFrequency<=935000000)NewValue=GlobalFrequency+120000;
    else if(GlobalFrequency<=1247000000)NewValue=GlobalFrequency+160000;
    else if(GlobalFrequency<=1870000000)NewValue=GlobalFrequency+240000;
    else NewValue=GlobalFrequency+480000;

    if(FreqIncr==1) NewValue=GlobalFrequency+1000000;
    if(FreqIncr==2) NewValue=GlobalFrequency+10000000;
    if(FreqIncr==3) NewValue=GlobalFrequency+100000000;
  }

  if(ButtonIsPushed(PushButtonDown)){//down count
    if(GlobalFrequency<=623000000)NewValue=GlobalFrequency-80000;
    else if(GlobalFrequency<=748000000)NewValue=GlobalFrequency-96000;
    else if(GlobalFrequency<=935000000)NewValue=GlobalFrequency-120000;
    else if(GlobalFrequency<=1247000000)NewValue=GlobalFrequency-160000;
    else if(GlobalFrequency<=1870000000)NewValue=GlobalFrequency-240000;
    else NewValue=GlobalFrequency-480000;

    if(FreqIncr==1) NewValue=GlobalFrequency-1000000;
    if(FreqIncr==2) NewValue=GlobalFrequency-10000000;
    if(FreqIncr==3) NewValue=GlobalFrequency-100000000;
  }

  if(ButtonIsPushed(PushButtonLeft)){//up count
     if(FreqIncr<3)FreqIncr++;
     return true;
  }

  if(ButtonIsPushed(PushButtonRight)){//down count
    if(FreqIncr>0)FreqIncr--;
    return true;
  }


    if(NewValue>=373000000 && NewValue<=3740000000){
      GlobalFrequency=NewValue;
      return true;
    }  
    return false;
}

void writeToReg(int Addr, int value){
  // take the chip select low to select the device:
  digitalWrite(chipSelectPin, LOW);

  SPI.transfer(Addr<<1|0x0); //Send Addr location with 0 for writing
  SPI.transfer(value);//send value
  // take the chip select high to de-select:
  digitalWrite(chipSelectPin, HIGH);
  
}

int readReg(int Addr){
  // take the chip select low to select the device:
  digitalWrite(chipSelectPin, LOW);

  SPI.transfer(Addr<<1|0x1); //Send Addr location with 1 for reading
  int inByte = SPI.transfer(0x0);//send value
  // take the chip select high to de-select:
  digitalWrite(chipSelectPin, HIGH);
  
  return inByte;
}

void printAllReg(){
  Serial.print("Reg0 0b");
  Serial.println(readReg(0x0), BIN); 
  Serial.print("Reg1 0b");
  Serial.println(readReg(0x1), BIN); 
  Serial.print("Reg2 0b");
  Serial.println(readReg(0x2), BIN); 
  Serial.print("Reg3 0b");
  Serial.println(readReg(0x3), BIN); 
  Serial.print("Reg4 0b");
  Serial.println(readReg(0x4), BIN); 
  Serial.print("Reg5 0b");
  Serial.println(readReg(0x5), BIN); 
  Serial.print("Reg6 0b");
  Serial.println(readReg(0x6), BIN); 
  Serial.print("Reg7 0b");
  Serial.println(readReg(0x7), BIN);
  Serial.print("Reg8 0b");
  Serial.println(readReg(0x8), BIN); 
  Serial.print("Reg9 0b");
  Serial.println(readReg(0x9), BIN); 
  Serial.print("RegA 0b");
  Serial.println(readReg(0xA), BIN);  
  Serial.print("RegB 0b");
  Serial.println(readReg(0xB), BIN); 
}

void printLCDFreq(unsigned long int f){
  lcd.clear();
  lcd.setCursor(0, 0);
  String myf_String=String(f);
  String myf_StringMHz=myf_String.substring(0,myf_String.length()-6)+String(".")+myf_String.substring(myf_String.length()-6,myf_String.length());
  Serial.println(myf_StringMHz);
  lcd.print(myf_StringMHz);
  lcd.setCursor(12, 0);
  lcd.print("MHz");
}

long setFrequencyHz(unsigned long f){
  
  unsigned long N=0;
  unsigned int OD=6;

  if(f<373000000){
    Serial.println("Frequency too low");
    return 0;
  }

  if(f>3740000000){
    Serial.println("Frequency too high");
    return 0;  
  }

  if(f<623000000){ //OD Div=6
    OD=6;
    //caluclate the RDiv
    N=(f*OD)/f_PFD;
  }
  else if(f<748000000){ //OD Div=5
    OD=5;
    //caluclate the RDiv
    N=(f*OD)/f_PFD;
  }
  else if(f<935000000){ //OD Div=4
    OD=4;
    //caluclate the RDiv
    N=(f*OD)/f_PFD;
  }
  else if(f<1247000000){ //OD Div=3
    OD=3;
    //caluclate the RDiv
    N=(f*OD)/f_PFD;
  }  
  else if(f<1870000000){ //OD Div=2
    OD=2;
    //caluclate the RDiv
    N=(f*OD)/f_PFD;
  }
  else if(f<=3740000000){ //OD Div=1
    OD=1;
    //caluclate the RDiv
    N=(f*OD)/f_PFD;
  }
  else{
    Serial.println("Frequency Error");
    return 0;
  }
  Serial.print("Value N Div:");
  Serial.println(N);

  Serial.print("Setting: ");
  Serial.print((f_PFD*N)/OD);
  Serial.println("Hz");


  unsigned int N_High=(N>>8)&0xFF;
  unsigned int N_Low=(N)&0xFF;

  writeToReg(0x2,0x0A);//turn off the output
  writeToReg(0x3,0x0); //Bdiv and RDivHigh
  writeToReg(0x4,R);//Rdiv low

  writeToReg(0x5,N_High);//N counter high
  writeToReg(0x6,N_Low); //Ncounter low
  
  writeToReg(0x7,0x63);
  delay(5);
  writeToReg(0x8,0b10111000|OD);//O divider last three bits
  writeToReg(0x9,0b10011011);
  writeToReg(0xA,0xC0);
  delay(5);
  writeToReg(0x2,0x08); //trun the output on

  //lcd.clear();
  //lcd.setCursor(0, 0);

  unsigned long int myf=((f_PFD*N)/OD); //we get wrong number when using double
  printLCDFreq(myf);

  return (f_PFD*N)/OD;
}

int checkError(){
  int err=readReg(0x0);
  String ErrStr="";
  if(err&0b1) ErrStr=String(ErrStr+"TLO ");
  if(err&0b10) ErrStr=String(ErrStr+"THI ");
  if(err&0b100) ErrStr=String(ErrStr+"LOCK ");
  if(err&0b1000) ErrStr=String(ErrStr+"ALCLO ");
  if(err&0b10000) ErrStr=String(ErrStr+"ALCHI ");
  if(err&0b100000) ErrStr=String(ErrStr+"UNLOCK ");
  lcd.setCursor(0, 1);
  lcd.print(ErrStr);
}


void loop() {

  printAllReg();
  

  setFrequencyHz(GlobalFrequency);

  bool NewFreq=false;
  unsigned int WaitCounter=0;
  unsigned int CheckErrCounter=0;


  while(true){
    //Serial.print("Reg0 0b");
    //Serial.println(readReg(0x0), BIN);

    
    delay(100);

    if(checkButtonAction()){//check if a button was pressed
      printLCDFreq(GlobalFrequency);
      lcd.setCursor(0, 1);
      lcd.print("Freq. Setup...");
      NewFreq=true;
      WaitCounter=0;
      
      //calculate the position of the dot, where the blinkin curser should appear
      if(GlobalFrequency/1000000000>0){
        lcd.setCursor(4-FreqIncr, 0);
      }
      else{
        lcd.setCursor(3-FreqIncr, 0);
      }

      
      lcd.blink(); 
    }

    //wait a litte untill we register the new frequency in the PLL
    if(NewFreq && WaitCounter>=20){
      setFrequencyHz(GlobalFrequency);
      NewFreq=false;
      lcd.noBlink(); 
    }

    if(CheckErrCounter>20 && !NewFreq){
      checkError();
      CheckErrCounter=0;
    }
    CheckErrCounter++;
    WaitCounter++;
    
    // send data only when you receive data:
    if (Serial.available() > 0) {
            // read the incoming byte:
            long incoming = Serial.parseInt();

            // say what you got:
            Serial.print("I received: ");
            Serial.println(incoming);

            setFrequencyHz(incoming*1000000);

           // printAllReg();
    }
     
  }

}

The whole generator was tested with a USB DVB-T stick, which I use as a spectrum analyzer together with gnu-radio [4].
Just ask Google “USB DVB-T stick spectrum analyzer” and you will find details.

This simple spectrum analyzer gives information about the output frequency. The output level should not be trusted.

An example spectrum is shown in the following:

LTC6946_OutputFreq

I use the output of the LTC6946 single ended.

When using the push buttons the user can choose to increase the frequency in 100 MHz, 10 Mhz, 1MHz or PFD/OD steps.
Even though the design aims for a R divider with 20 I use it with 41 this gives a 80kHz steps in the lowest frequencies.
And so far I have not seen any problems. For smaller steps a different reference frequency and different loop filter components are probably needed.

Standalone operation

The frequency generator is designed for stand alone operation, but can also be controlled by the serial port.
In stand alone mode a LCD display shows the frequency and the status of then PLL, like if it is locked, unlocked a.s.o.
The status information are extracted from the 0x00 register.

LTC6946_FreqSetupLock

When pressing a button to change the frequency a courser appears on the display to select which frequency steps to change.
The display shows “Freq Setup …” and waits for input. After the frequency is set and no further action appears the frequency setting is written into the register and appears on the RF output connectors.

LTC6946_FreqSetup

The code and design can be found on GitHub:

git clone https://github.com/digibird1/LTC6946FreqGen

[1] http://www.linear.com/product/LTC6946
[2] https://www.fairchildsemi.com/products/discretes/fets/mosfets/BSS138.html
[3] http://kicad-pcb.org/
[4] http://gnuradio.org/

Advertisements
%d bloggers like this: