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

The schematic looks the following:

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

The finished 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 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(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;
}

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

SPI.transfer(value);//send value
// take the chip select high to de-select:
digitalWrite(chipSelectPin, HIGH);

}

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

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.print("Reg1 0b");
Serial.print("Reg2 0b");
Serial.print("Reg3 0b");
Serial.print("Reg4 0b");
Serial.print("Reg5 0b");
Serial.print("Reg6 0b");
Serial.print("Reg7 0b");
Serial.print("Reg8 0b");
Serial.print("Reg9 0b");
Serial.print("RegA 0b");
Serial.print("RegB 0b");
}

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(){
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");

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

}

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

if(CheckErrCounter>20 && !NewFreq){
checkError();
CheckErrCounter=0;
}
CheckErrCounter++;
WaitCounter++;

// send data only when you receive data:
if (Serial.available() > 0) {
long incoming = Serial.parseInt();

// say what you got:
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:

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.

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.

The code and design can be found on GitHub:

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