ADF4351 (35MHz to 4.4GHz)
Synthesiser Interface
There was a discussion on the Positron8 BASIC compiler's forum about an interesting frequency synthesiser board that could span from 35MHz to 4.4GHz, and contains an ADF4351 chip. A few of the users on the forum asked if there was a set of routines for the compiler that performed the interface. This interested me, but I couldn't afford to buy one of the boards to create an interface. Then one of our good users sent me a board, so I could try my hand at it. So this is the result, and many thanks to Charlie for the board. It means a lot when one compiler user helps another.
The board looks like the image below, and is available from many places on the internet:
Doing a search on the internet, I came across a lot of sites that go way over the top about the interface mechanism to the ADF4351 chip, and talk far too much about things that simply don't matter. :-) The board uses a simple SPI interface and contains six 32-bit registers. These allow a whole host of changes to the frequency, but the vast majority of users simply want a frequency output, so I created a library to do just that!
The demo program to use the library is listed below:
'
' Interface with an ADF4351 based 35MHz to 4.4GHz PLL synthesiser module.
' Written by Les Johnson for the Positron8 BASIC compiler
'
Include "Amicus18.inc" ' We'll use the Amicus18 development board, which is a PIC18F25K20 running at 64MHz
'
' Setup the pins to use for the interface (These must be placed before the ADF4351 library is included in the program)
'
$define ADF_DAT_Pin PORTB.0 ' Connect to the ADF4351 DAT pin
$define ADF_CLK_Pin PORTB.1 ' Connect to the ADF4351 CLK pin
$define ADF_LD_Pin PORTB.2 ' Connect from the ADF4351 Lock Detect (LD) pin
$define ADF_LE_Pin PORTB.3 ' Connect to the ADF4351 LE pin
Include "ADF4351.inc" ' Load the ADF4351 interface routines into the program
Dim Frequency As Float ' Holds the frequency for the demo code
'---------------------------------------------------------------------------
' Sweep the frequency from 88.0 MHz to 108.0 MHz in 10KHz steps
'
Main:
Do ' Create an infinite loop
For Frequency = 88.0 To 108.0 Step 0.01 ' Create a loop for the frequency scan
Do ' Create a loop
ADF_SetFreq(Frequency) ' Alter the frequency of the ADF4351 board
If ADT_tTimedOut = True Then ' Did the PLL lock time out, while waiting?
PLL_TimeOut() ' Yes. So send a message to the serial terminal
EndIf
Loop Until ADF_tLock = True ' Do it again if the PLL frequency is not locked
DelayMS 10 ' A small delay between frequency changes
Next ' Close the frequency scan loop
Loop ' Do it forever
'---------------------------------------------------------------------------
' Come here if the PLL lock timed out
' Transmit a message to a serial terminal
'
Proc PLL_TimeOut()
HRSOutLn "PLL Lock Timed out"
EndProc
The demo program sweeps from frequency 88MHz to 108MHz. This is because I do not own a frequency meter, so I used a simple VHF FM radio tuned to a particular frequency, then set the ADF4351 to the same frequency, and heard the hiss drop from the radio, meaning the frequency was spot on. Simple, but effective. The main subroutine to use in the library is ADF_SetFreq. This alters the frequency output of the ADF4351 chip. It requires a floating point value for the MHz to transmit. It's as simple as that! The pin $defines tell the library which pins to use for the ADF4351 SPI interface.
Note. On the board used, the CE pin also had to be connected to the 3.3 Volt line, as it was floating. The CE line is the Chip Enable pin for the ADF4351 device, where a low (Gnd) disables the chip, and a high (3.3 Volts) enables the chip.
The image below is the ADF4351 board attached to an Amicus18 development board, which carries a PIC18F25K20 chip, operating at 3.3 Volts at a frequency of 64MHz, so it's perfect for the ADF4351 board, which also requires no more than 3.3 Volts. Unfortunately, I could not find a circuit diagram for the board anywhere. There are many similar boards available that have a circuit diagram, but not the one above. However, they will all follow the same circuitry, as this is most probably a reference circuit clone.
The green, illuminated, LED indicates that the frequency required is locked and is transmitting. The red LED is simply a power indicator.
The interface library code listing is below, but can also be downloaded from here: ADF4351 Library and Demo.
$ifndef _ADF4351_INC_
$define _ADF4351_INC_
'
' Interface with an ADF4351 based 35MHz to 4.4GHz PLL synthesiser module.
' Written by Les Johnson for the Positron8 and Positron16 BASIC compilers
'
' My special thanks to Charlie for sending me the synthesiser board.
'
' Setup the default pins to use for the interface
'
$ifndef ADF_DAT_Pin ' Has the DAT pin been setup in the main program?
$define ADF_DAT_Pin PORTB.0 ' No. So connect to the ADF4351 Dat pin
$endif
$ifndef ADF_CLK_Pin ' Has the CLK pin been setup in the main program?
$define ADF_CLK_Pin PORTB.1 ' No. So connect to the ADF4351 Clock (CLK) pin
$endif
$ifndef ADF_LD_Pin ' Has the LD pin been setup in the main program?
$define ADF_LD_Pin PORTB.2 ' No. So connect from the ADF4351 Lock Detect (LD) pin
$endif
$ifndef ADF_LE_Pin ' Has the LE pin been setup in the main program?
$define ADF_LE_Pin PORTB.3 ' No. So connect to the ADF4351 LE pin
$endif
$ifndef True
$define True 1
$endif
$ifndef False
$define False 0
$endif
'
' Create variables
'
Dim ADF_bFlags As Byte Access ' Holds flag bits
Dim ADF_tLock As ADF_bFlags.0 ' True when the ADF4351 PLL locks
Dim ADT_tTimedOut As ADF_bFlags.1 ' True if the lock times out
Dim ADF_bBitCount As Byte Access ' Bit counter for the SPI interface
Dim ADF_bRegIndex As Byte ' ADF4351 Register Counter
Dim ADF_bRFODivider As Byte ' RF output divider value (1 - 64)
Dim ADF_dIntDiv As Dword ' Integer division factor
Dim ADF_dFracDiv As Dword ' Numerator of the fractional division
Dim ADF_dRegs[6] As Dword ' Array for ADF4351 control registers
$if _core = 24 Or _core = 33 ' Is the device a 16-bit core type?
Dim ADF_dReg0 As ADF_dRegs_0 ' Register 0
Dim ADF_dReg1 As ADF_dRegs_1 ' Register 1
Dim ADF_dReg2 As ADF_dRegs_2 ' Register 2
Dim ADF_dReg3 As ADF_dRegs_3 ' Register 3
Dim ADF_dReg4 As ADF_dRegs_4 ' Register 4
Dim ADF_dReg5 As ADF_dRegs_5 ' Register 5
$else ' Otherwise... The device is an 8-bit type
Dim ADF_dReg0 As ADF_dRegs#0 ' Register 0
Dim ADF_dReg1 As ADF_dRegs#1 ' Register 1
Dim ADF_dReg2 As ADF_dRegs#2 ' Register 2
Dim ADF_dReg3 As ADF_dRegs#3 ' Register 3
Dim ADF_dReg4 As ADF_dRegs#4 ' Register 4
Dim ADF_dReg5 As ADF_dRegs#5 ' Register 5
$endif
Symbol cADF_RefFrequency = 25.0
Symbol cADF_OutRes = 0.01
Symbol cADF_FracMod = (cADF_RefFrequency / cADF_OutRes) ' Fractional modulus (2 to 4095)
Symbol cADF_FracModSh3 = (cADF_FracMod << 3)
$define ADF_cSPIDelay() DelayCS 10 ' The very small delay used in the SPI interface
'-----------------------------------------------------------------
' Enable the ADF4351 device (if applicable)
' Input : None
' Output : None
' Notes : Is only applicable if the CE pin is defined in the main program
'
$define ADF_Enable() '
$ifdef ADF_CE_Pin '
PinSet ADF_CE_Pin '
$endif
'-----------------------------------------------------------------
' Disable the ADF4351 device (if applicable)
' Input : None
' Output : None
' Notes : Is only applicable if the CE pin is defined in the main program
'
$define ADF_Disable() '
$ifdef ADF_CE_Pin '
PinClear ADF_CE_Pin '
$endif
'-----------------------------------------------------------------
' Wait until the ADF4351 PLL is in locked state
' Input : pTimeout holds the timeout value (in approx milliseconds)
' Output : ADF_tLock true if the chip is locked
' : ADT_tTimedOut true if a lock has not happened and the loop has timed out
' Notes : None
'
Proc ADF_WaitForPLL_Lock(pTimeout As Word)
ADF_tLock = False ' Default to no lock
ADT_tTimedOut = False ' Default to no timeout
Repeat ' Create a loop to wait for the PLL lock
If pTimeout = 0 Then ' Has the timeout counter reached 0?
ADT_tTimedOut = True ' Yes. So signal a timeout
Return ' And exit the subroutine
EndIf
DelayMS 1 ' Delay for 1 ms
Dec pTimeout ' Decrement the timeout counter
Until ADF_LD_Pin = 1 ' Wait until the LD pin is high
ADF_tLock = True ' If the program has gotten here, a lock has occured
EndProc
'------------------------------------------------------------------
' SPI mode 00 interface to the ADF4351
' Input : pData
' Output : None
' Notes : None
'
Proc ADF_WriteSPI(pData As Dword)
For ADF_bBitCount = 31 To 0 Step -1 ' Create a loop for the 32-bits of data to send
PinClear ADF_DAT_Pin ' Default to a clear bit
If pData.31 = 1 Then ' Is the bit of the value to send a 1?
PinSet ADF_DAT_Pin ' Yes. So place the outgoing bit onto the ADF_DAT_Pin
EndIf
ADF_cSPIDelay() ' A small delay
pData = pData << 1 ' Shift the next bit into MSB
PinSet ADF_CLK_Pin ' Set the SCK pin high
ADF_cSPIDelay() ' A small delay
PinClear ADF_CLK_Pin ' Pull the SCK pin low
Next
EndProc
'-----------------------------------------------------------------
' Send the values to the six ADF4351 registers
' Input : Array ADF_dRegs holds the 6 register values
' Output : ADF_tLock True if the chip is locked
' : ADT_tTimedOut true if a lock has not happened and the loop has timed out
' Notes : None
'
Proc ADF_WriteRegData()
For ADF_bRegIndex = 5 To 0 Step -1 ' Create a loop for all 6 registers
PinClear ADF_LE_Pin ' Pull the LE pin low to start a transaction
ADF_WriteSPI(ADF_dRegs[ADF_bRegIndex]) ' Send the 32-bit register data
PinSet ADF_LE_Pin ' Set the LE pin high to load it into a register
DelayUS 100 ' Delay for 100us to make sure data is latched
PinClear ADF_LE_Pin ' Pull the LE pin low
Next
ADF_WaitForPLL_Lock(10000) ' Wait until the ADF4351 locks
EndProc
'-----------------------------------------------------------------
' Calculate ADF4351 register settings for a new frequency
' Input : pFreq holds the frequency (in MHz)
' Output : ADF_dReg0 to ADF_dReg5 hold the register values
' : ADT_tTimedOut true if a lock has not happened and the loop has timed out
' Notes : None
'
Proc ADF_SetFreq(pFreq As Float)
'
' Setup ADF_dReg4 (register 4)
'
ADF_dReg4 = $0000803C ' Setup the default value for register 4
Select pFreq ' What frequency range is required?
Case 2200.0 To 4400.0 ' Is it 2200MHz to 4400MHz?
ADF_bRFODivider = 1 ' Yes. So set the divider value to 1
ADF_dReg4.Byte2 = $8C ' Alter byte 2 of register 4
Case 1100.0 To 2200.0 ' Is it 1100MHz to 2200MHz?
ADF_bRFODivider = 2 ' Yes. So set the divider value to 2
ADF_dReg4.Byte2 = $9C ' Alter byte 2 of register 4
Case 550.0 To 1100.0 ' Is it 550MHz to 110MHz?
ADF_bRFODivider = 4 ' Yes. So set the divider value to 4
ADF_dReg4.Byte2 = $AC ' Alter byte 2 of register 4
Case 275.0 To 550.0 ' Is it 275MHz to 550MHz?
ADF_bRFODivider = 8 ' Yes. So set the divider value to 8
ADF_dReg4.Byte2 = $BC ' Alter byte 2 of register 4
Case 137.5 To 275.0 ' Is it 137.5MHz to 275MHz?
ADF_bRFODivider = 16 ' Yes. So set the divider value to 16
ADF_dReg4.Byte2 = $CC ' Alter byte 2 of register 4
Case 68.75 To 137.5 ' Is it 68.75MHz to 137.5MHz?
ADF_bRFODivider = 32 ' Yes. So set the divider value to 32
ADF_dReg4.Byte2 = $DC ' Alter byte 2 of register 4
Case 35.0 To 68.75 ' Is it 35MHz to 68.75MHz?
ADF_bRFODivider = 64 ' Yes. So set the divider value to 64
ADF_dReg4.Byte2 = $EC ' Alter byte 2 of register 4
Case Else ' Otherwise. The frequency is not recognised. So...
Return ' Exit the routine without making changes
EndSelect
'
' Calculate the values for ADF_dIntDiv and ADF_dFracDiv
'
ADF_dIntDiv = (pFreq * ADF_bRFODivider) / cADF_RefFrequency
ADF_dFracDiv = (((pFreq * ADF_bRFODivider) / cADF_RefFrequency) - ADF_dIntDiv) * cADF_FracMod
HRSOutLn "cADF_FracMod = ", Dec cADF_FracMod
HRSOutLn "pFreq = ", Dec1 pFreq
HRSOutLn "ADF_dIntDiv = ", Dec ADF_dIntDiv
HRSOutLn "ADF_dFracDiv = ", Dec ADF_dFracDiv
'
' Setup ADF_dReg0 (register 0)
'
ADF_dReg0 = ADF_dIntDiv << 15 ' Move ADF_dIntDiv into bits 15 - 30
ADF_dReg0 = ADF_dReg0 + (ADF_dFracDiv << 3) ' Insert ADF_dFracDiv to bits 3 - 14
'
' Setup ADF_dReg1 (register 1)
'
ADF_dReg1 = $08008000 ' Set the default value
ADF_dReg1 = ADF_dReg1 + cADF_FracModSh3 ' Move cADF_FracMod into bits 3 - 14
Inc ADF_dReg1 ' Add 1 for reg addr
'
' Setup ADF_dReg2, ADF_dReg3 and ADF_dReg5 (registers 2, 3, and 5)
'
ADF_dReg2 = $00004E42 ' MUX output disabled
ADF_dReg3 = $000004B3 ' PD polarity positive, clk divider off
ADF_dReg5 = $00580005 ' LD pin set for dig ADF_tLock detect
ADF_WriteRegData() ' Write the new register values to the ADF4351 chip
EndProc
'-----------------------------------------------------------------
' Setup the program and the ADF4351 SPI interface
' Input : None
' Output : None
' Notes : None
'
Proc ADF_Setup()
$ifdef ADF_CE_Pin ' Has the CE pin been setup in the main program?
High ADF_CE_Pin ' Yes. So set the CE pin to high
$endif
DelayMS 500 ' Wait for the ADF4351 board to settle after power up
Low ADF_DAT_Pin ' Pull the DAT pin to low
Low ADF_CLK_Pin ' Pull the CLK pin to low
High ADF_LE_Pin ' Set the LE pin to high
Input ADF_LD_Pin ' Make the LD pin an input
ADT_tTimedOut = False ' Locked time out flag (true if timed out)
ADF_tLock = False ' ADF4351 PLL Lock status (true if locked)
Clear ADF_dRegs ' Clear the registers array
EndProc
'---------------------------------------------------------------------------
' The Starting part of the library
'
ADF_Main:
ADF_Setup() ' Setup the program and the SPI interface
$endif ' _ADF4351_INC_
For the users that want to add to the library, or want to see more details concerning the registers within the ADF4251 chip, here is the datasheet for it: ADF4351_Datasheet