//
// Test code for the ADF4153 PLL with integrated VCO
// using an LCD shield to control the output frequency.
//
// (c) Copyright 2018 Wolfgang Scherr, OE8WOZ
//
// Use at your own risk! --> EARLY ALPHA RELEASE WITH MINIMUM TEST.
//
// $Id: ADF4153_LCD.ino 1122 2018-07-24 09:50:24Z wolfi $
//
// ===============================================================
//
// You may use this code complete or partly for your ham radio
// projects, as long as you do it at your own risk. I am not
// responsible for any damage (direct or indirect) caused by it.
// Commercial use (e.g. for paid work) of this code is prohibited!
//
// Be aware this PLL boards emit RF power even without anything
// connected and may disturb critical frequencies. Take care.
//
// Please notify the me when using and/or distributing the code:
// mail oe8woz at
// I can only implement improvements/fixes based on feedback I,
// receive. This is also in the sense of the ham radio spirit.
//
// BEER-WARE: if you like the code and use it (partly) for your
// projects, you may pay me a pint of beer when we meet at some
// ham radio event or maker faire somewhere in Europe. TNX! :-D
//
// ===============================================================
//
// External connections required:
//
// Use LCD Arduino shield and SPI connections (CLK, MOSI) to e.g.
// these NORT PLL modules.
//
// Please take care, the PLL module requires 3.3V levels only,
// so a resistive divider of 1.8k to 3.3k provides ~3.3V at 5V:
//
// .......
// . A
// +-*---*----------------*-- GND -. D
// | | | | . F
// | = = = . 4
// | 3 3 3 . 1
// | k k k . 5
// | 3 3 3 . 3
// | = = = . N
// | | | | . P O
// | *---|----------------|-- CLK -. L R
// | | *----------------|-- DAT -. L T
// | | | *-- LE -.
// | | +-|----------------|-- MUX -. B
// | | | | | . O
// | = | = = . A
// | 1 | 1 1 . R
// | k | k ......LCD..... k ~9V -. D
// | 8 | 8 B . . D D D D 8 .......
// | = | = C E R B B B B =
// | | | | K N S 7 6 5 4 |
// ----------------------------------------------------
// | # # # # # # # # # # # # # # # # # # |
// | S S A G 1 1 1 1 0 0 0 0 0 0 0 0 0 0 |
// | C D R N 3 2 1 0 9 8 7 6 5 4 3 2 1 0 |
// | L A E D T R |
// | F X X |
// |===== |
// | = |
// |USB = |
// | = ARDUINO UNO |
// |===== |
// | |
// | |
// | |
// | R 3 G G V |
// |======== n 5 E V 5 N N I A A A A A A |
// ~9V -|DC-CON = c V S 3 V D D N 0 1 2 3 4 5 |
// |======== # # # # # # # # # # # # # # |
// ----------------------------------------------------
// K
// E
// Y
// LCD
//
//
// Alternative LE output and MUX input can be set below in the code.
// Also the reference frequency can be set in the code (constant).
//
// NORT PLL Connection:
//
// ----------------------------------------------------
// | |
// | |
// +9V --- | 6 |
// GND --- | 5 |
// MUX --- | 4 (inverted to ADF MUX!) |--\ RF
// LE --- | 3 |--/ OUT
// DAT --- | 2 |
// CLK --- | 1 |
// | |
// | |
// ----------------------------------------------------
//
// Please verify connection by checking GND first with Ohm-Meter
// (GND pin is connected to case) other pins follow accordingly.
//
// ===============================================================
//
// Usage:
//
// .) use left/right button to select the decimal position of the
// frequency
// .) use up/down button to increment/decrement decimal position
// (the frequency will be immediately changed)
// .) if a decrement is below or an increment above the limits,
// the limit frequency is set
//
// .) for debugging, use serial monitor in Arduino (115200 Baud)
//
// ===============================================================
//
// TODO:
//
// .) allow variable reference frequency
// .) allow configuring PFD frequency division/multiplication
// .) check background LED lighting feature of some shields
// .) test, test, test, ...
//
// ===============================================================
// uncomment either line: upper means no debug, lower adds serial debug (max. 80ch/line)
//#define DEBUG(...) {}
#define DEBUG(...) {char s[80]; sprintf(s,__VA_ARGS__); Serial.println(s);}
//=====================================================================
// INCLUDES, DEFINITIONS, GLOBAL VARIABLES
//=====================================================================
// we use the LCD shield, the AVR EEPROM and SPI interface
#include
#include
#include
// may vary, depending on setup
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
#define LCD_BACKLIGHT 10
typedef enum {NONE, UP, DOWN, LEFT, RIGHT, SET} key_t;
// some may vary, depending on setup (only SPI pins are hardcoded)
#define ADF4153_LE 3 // ADF select
#define ADF4153_MUX 12 // lock detect
// reference used on PLL board, you may need to adapt this
const uint32_t REFin = 20000000; // Hz
// set to "real" reference freq.
// PLL parameters kept global, may be used by several functions
uint32_t fpfd; // internal phase comparison frequency
uint8_t D, R, CP; // only used in INIT for now
uint16_t MOD; // only used in INIT for now, 12bit
uint16_t PLL_R02_RES, PLL_R02; // calculate once, then reuse
uint32_t PLL_R01; // calculate once, then reuse
// global variables for GUI handling
// GGMMMkkk...LL in Hz
uint64_t RFout = 12000000000LL; // output frequency
uint64_t steps = 1000000LL; // actual value for increment/decrement of freq.
int setpos=7; // position on screen, must match with steps above
//=====================================================================
// GENERIC ROUTINES (user I/O)
//=====================================================================
// read key value and translate (may need to further adapt)
key_t fetchKeys() {
int key_val = analogRead(0);
if (key_val < 50)return RIGHT;
if (key_val < 195)return UP;
if (key_val < 380)return DOWN;
if (key_val < 555)return LEFT;
if (key_val < 790)return SET;
return NONE;
}
// update frequency display
void showFreq(const uint64_t freq) {
char s[13];
// show frequency
lcd.setCursor(1, 0);
sprintf(s,"%5ld.%06ld",(uint32_t)(freq/1000000L),(uint32_t)(freq%1000000L));
lcd.print(s);
// show lock state (on NORT this signal is inverted)
lcd.setCursor(0, 1);
lcd.print(digitalRead(ADF4153_MUX)?F("nolock"):F("#LOCK#"));
}
//=====================================================================
// PLL BOARD ROUTINES (handling the ADF4153)
//=====================================================================
// write an ADF register (24bit)
void writeRegADF24(const uint32_t value)
{
// 24bit transfer
SPI.transfer((value >> 16) & 0xFF);
SPI.transfer((value >> 8) & 0xFF);
SPI.transfer((value >> 0) & 0xFF);
digitalWrite(ADF4153_LE, HIGH);
digitalWrite(ADF4153_LE, LOW);
// show what we have written
DEBUG("%06lx",value);
}
// write an ADF register (16bit)
void writeRegADF16(const uint16_t value)
{
// 16bit transfer
SPI.transfer((value >> 8) & 0xFF);
SPI.transfer((value >> 0) & 0xFF);
digitalWrite(ADF4153_LE, HIGH);
digitalWrite(ADF4153_LE, LOW);
// show what we have written
DEBUG("%04x",value);
}
// Program all the registers of the ADF4153 the first time
void initADF()
{ DEBUG("REF: %ld.%06ld MHz",(long)(REFin/1000000L),(long)(REFin%1000000L));
// some initial global settings
MOD = 4000; // we use this for a step size of 10MHz/4000=2.5kHz
// x4 allows a step size of 10kHz on the output
CP = 7; // we set max. current (should be optimized!)
// we won't set PFD higher than the given 20MHz (32MHz would be MAX)
D = 0;
R = 2;
// PFD is derived from above settings
fpfd = REFin*(1+D)/R;
DEBUG("PFD: %ld.%06ld MHz",(long)(fpfd/1000000L),(long)(fpfd%1000000L));
// show in readable format
DEBUG("MOD = %d",MOD);
// we re-use them later in the frequency set function as well, so define it once here
PLL_R02 = 0x02|(0x0<<12)|(D<<11)|(CP<<7)|(1<<6)|(1<<5); // no phase resync, neg. CP, long lock
PLL_R02_RES = PLL_R02|(1<<2); // PLL reset
PLL_R01 = 0x01L|(1L<<20)|(1L<<18)|((uint32_t)R<<14)|((uint32_t)MOD<<2); // presc. 8/9 (>2GHz), dig. lock out, no resync
// update registers
writeRegADF16(0x3); // R02: reset register
//writeRegADF16(0x3|0x3C4); // R02: low noise mode (select only one of these three)
writeRegADF16(0x3|0x380); // R02: low noise and spur mode (select only one of these three)
//writeRegADF16(0x3|0x000); // R02: low spur mode (select only one of these three)
writeRegADF16(PLL_R02_RES);
writeRegADF24(PLL_R01);
writeRegADF24(100L<<14); // just some value, we nevertheless rewrite when setting the frequency
}
// Set a new frequency on the ADF4153
void updateADF(uint64_t freq) {
uint16_t INTR, FRACR; // 9bit, 12bit
DEBUG("OUT: %ld.%06ld MHz",(long)(freq/1000000L),(long)(freq%1000000L));
// this is given by the NORT PLL output doublers (two times)
freq>>=2;
DEBUG("VCO: %ld.%06ld MHz",(long)(freq/1000000L),(long)(freq%1000000L));
// calculate registers for fout
INTR = freq/fpfd;
uint32_t remainder = ((freq-INTR*fpfd)<<16)/fpfd;
FRACR = (remainder*MOD+32768)>>16; // do rounding
DEBUG("INT = %d",INTR);
DEBUG("FRAC = %d",FRACR);
// update registers, acc. to specification
writeRegADF16(2L|PLL_R02_RES); //R2 DB2=1
writeRegADF24(1L|PLL_R01); //R1 fixed modulus
writeRegADF24(0L|((uint32_t)INTR<<14)|((uint32_t)FRACR<<2)); //R0 normal mode
writeRegADF16(2L|PLL_R02); //R2 DB2=0
delay(3);
}
//=====================================================================
// ARDUINO SETUP CALL (mandatory, do HW setup, do ADF4153 setup)
//=====================================================================
void setup() {
// Init PLL I/O
pinMode(ADF4153_MUX, INPUT);
pinMode(ADF4153_LE, OUTPUT);
digitalWrite(ADF4153_LE, HIGH);
// Init PLL SPI bus
SPI.begin();
SPI.setDataMode(SPI_MODE0);
SPI.setBitOrder(MSBFIRST);
// Init serial (for debug)
Serial.begin(115200);
// Init LCD
pinMode(LCD_BACKLIGHT, OUTPUT);
digitalWrite(LCD_BACKLIGHT, LOW);
lcd.begin(16, 2);
lcd.display();
analogWrite(10,255); // LCD background on
// Splash screen
lcd.setCursor(0, 0);
lcd.print(F(">ADF4153 master<"));
lcd.setCursor(0, 1);
lcd.print(F("(C) 2018 OE8WOZ"));
delay(1000);
lcd.setCursor(0, 0);
lcd.print(F(">GGMMM.kkk...MHz"));
lcd.setCursor(0, 1);
lcd.print(F("NOLOCK STO #0"));
lcd.blink();
// start-up message
Serial.println();
Serial.println("ADF4153 master, (c) 2018 OE8WOZ");
// restore last setting, if not initial startup
if (EEPROM.read(0)==0x5A) {
EEPROM.get(1,RFout);
} else {
EEPROM.write(0,0x5A);
EEPROM.put(1,RFout);
}
// Init PLL
digitalWrite(ADF4153_LE, LOW);
initADF();
delay(10);
updateADF(RFout);
delay(10);
showFreq(RFout);
lcd.setCursor(12-setpos,0);
}
//=====================================================================
// ARDUINO LOOP CALL (mandatory, reads keys and update ADF4153)
//=====================================================================
void loop() {
int upd=0;
// handle user entry
key_t key = fetchKeys();
if (key!=NONE) {
if (key==LEFT) {
if (setpos<11) {
++setpos;
steps *= 10;
}
if (setpos==6) ++setpos;
upd = 2;
}
if (key==RIGHT) {
if (setpos>4) { // min. step is 10kHz
--setpos;
steps /= 10;
}
if (setpos==6) --setpos;
upd = 2;
}
if (key==UP) {
RFout += steps;
// not so elegant, but works (theoretical max.)
if (RFout>16000000000LL) RFout=16000000000LL;
upd = 1;
}
if (key==DOWN) {
RFout -= steps;
// not so elegant, but works (theoretical min.)
if (RFout<2000000000LL) RFout=2000000000LL;
upd = 1;
}
}
// handle frequency change
if (upd==1) {
updateADF(RFout);
EEPROM.put(1,RFout);
}
// handle LCD/key update
if (upd>0) {
showFreq(RFout);
lcd.setCursor(12-setpos,0);
delay(250); // delay for potential key repeat
}
}