///////////////////////////////////////////////////////////////////////////// // maths to generate register values for the ADF5355 // this must run on somthing with high precision // floating point capability // Arduino DUE or ZERO most excellent // not sure about anything else // also watch out for 5V logic and the 3.3V ADF5355 // perhaps you need potential dividers on your logic lines? // // This bad magic is by G0MGX and its the 1st Jan 2020 // // Written to receive a frequency in MHz as standard ASCII // over the Serial1 line - in my case this is being sent by // a MicroMite LCD backpack with a fab and groovy touch // screen interface // // Serial1 is on digital pins 0 and 1 and connect to RX and TX respectively // // 4th Jan 2020 changes to SPI writing routine // 6th Jan 2020 changes to Mod2 and ADC Clock calculations // 7th Jan 2020 changes to add init sequence as per datasheet page 33 ///////////////////////////////////////////////////////////////////////////// // constants are OK as global #include // SPI library for comms to the ADF5355 // datasheet suggests 50MHz max for clock speed // here its set at 10MHz which seems lightning fast SPISettings MySettings(10000000, MSBFIRST, SPI_MODE0); const int CS5355 = 10; // SPI-Chip Select ADF5355 pin D10 const int DEBUG = 0; // set to 1 for debug info via USBSerial const long refin = 100; // Ref Frequency = 100.000 Mhz const float initFrequency = 1000.0; // initial target frequency const float CHSPFreq = 0.2; // initial channel separation value MHz const int DIV2ON = 1; // constants used for divider values const int DIV2OFF = 0; const int AUTOCALON = 1; // AutoCal values const int AUTOCALOFF = 0; const int COUNTERRESETON = 1; // counter reset const int COUNTERRESETOFF = 0; // CalcGCD does what is says on the tin long CalcGCD (int A, int B) { int T; while (B!=0) { T = B; B = A % B; A = T; } return A; } // ConvertFreq takes the value in TargetFrequency and // converts it to 13 register values void ConvertFreq(unsigned long R[], double TargetFrequency, int R_divider, int AutoCalEnable, int CounterReset) { // the following are components of the ADF5355 registers // you should check the datasheet for more info // you can set any of these values and they will be reflected in // the final hex register calculations int Prescaler = 0; // 1bit 0 = 4/5 1 = 8/9 int Autocal = AutoCalEnable; // 1bit int mod2 = 0; // 14 bit int CountRes = CounterReset; // 1bit int CP3_State = 0; // 1bit int Power_Down = 0; // 1bit int PD_Pola = 1; // 1bit int Mux_Log = 1; // 1bit int Ref_Mode = 0; // 1bit 1 = differential 0 = single int Charge_Pump = 9; // 4bit int Double_Buf = 0; // 1bit int R_Counter = 1; // 10bit int R_Div2 = R_divider; // 1bit int Ref_Doubler = 0; // 1bit int M_Muxout = 6; // 3bit int OutPWR = 3; // 2bit OutPwr 0-3 3= +5dBm int RF_Enable = 1; // 1bit RF_Enable 1 = on 0 = off int Reserved = 0; // 3bit int RF_Out_B = 0; // 1bit int MTLD = 0; // 1bit 0 = Disabled 1 = Enabled int CPBleed = 126; // 8bit int RF_Div_Sel = 3; // 3bit int Feedback = 1; // 1bit 1 = Fundamental 0 = Divided // // below 6.8GHz use output A above B // determine divisor based on frequency // and set output port // this idea is stolen from GM8BJF // who is far cleverer than I // float outdiv = 1; if (TargetFrequency >= 6800) { outdiv = 0.5; RF_Div_Sel = 0; RF_Out_B = 0; RF_Enable = 0; } if (TargetFrequency < 6800) { outdiv = 1; RF_Div_Sel = 0; RF_Out_B = 1; RF_Enable = 1; } if (TargetFrequency < 3400) { outdiv = 2; RF_Div_Sel = 1; RF_Out_B = 1; RF_Enable = 1; } if (TargetFrequency < 1700) { outdiv = 4; RF_Div_Sel = 2; RF_Out_B = 1; RF_Enable = 1; } if (TargetFrequency < 850) { outdiv = 8; RF_Div_Sel = 3; RF_Out_B = 1; RF_Enable = 1; } if (TargetFrequency < 425) { outdiv = 16; RF_Div_Sel = 4; RF_Out_B = 1; RF_Enable = 1; } if (TargetFrequency < 212.5) { outdiv = 32; RF_Div_Sel = 5; RF_Out_B = 1; RF_Enable = 1; } if (TargetFrequency < 106.25) { outdiv = 64; RF_Div_Sel = 6; RF_Out_B = 1; RF_Enable = 1; } /////////////////////////////////////////////////////////////////////////////////// //////////////////////// N and Frac1 and Frac2 calculations ///////////////// //////////////////////// Done using double precision FP maths ///////////////// //////////////////////// G4JNT has a calculator that this matches ///////////////// //////////////////////// and Andy is much cleverer than I ///////////////// //////////////////////// so I'm sticking with this as it works ///////////////// /////////////////////////////////////////////////////////////////////////////////// double PFDFreq = 0.0; double N = 0.0; int N_Int = 0; int FracN_Int = 0; double Frac1_double = 0.0; double Frac2_double = 0.0; int Frac1_Int = 0; int Frac2_Int = 0; double ADCClkDiv = 0.0; int ADCClkDiv_Int = 0; long GCD = 0; PFDFreq = refin * ((1.0 + Ref_Doubler) / (R_Counter * (1.0 + R_Div2))); // Phase detector frequency // should be the ref frequency in our case // unless you can think of a very good reason why not N = (TargetFrequency * outdiv) / PFDFreq; // Calculate N N_Int = N; // Turn N into integer by casting - no rounding Frac1_double = (N - N_Int) * pow(2, 24); // Calculate Frac1 (N remainder * 2^24) FracN_Int = Frac1_double; // turn Frac1 into an integer by casting GCD = CalcGCD(int(PFDFreq*1000), int(CHSPFreq*1000)); mod2 = PFDFreq * 1000.0 / GCD; // calculate mod2 based on channel spacing Frac2_double = ((Frac1_double - FracN_Int)) * mod2; // Claculate Frac2 (FracN_Int remainder * mod2) Frac1_Int = Frac1_double; // turn Frac1 into integer by casting Frac2_Int = Frac2_double; // turn Frac2 into integer by casting ADCClkDiv = (((PFDFreq * 1000000) / 100000) - 2) / 4; // calculate ADC clock divider - must round UP ADCClkDiv = ceil(ADCClkDiv); ADCClkDiv_Int = int(ADCClkDiv); if (ADCClkDiv_Int > 255) ADCClkDiv_Int = 255; ////////////////// Set 32 bit register values R0 to R12 /////////////////////////// // nothing much clever going on here // note that multiply by 2 to the something is a bit shift // note that adding 2 to the somthing is setting an individual bit ////////////////////////////////////////////////////////////////////////////////// R[0] = (unsigned long) (0 + N_Int * pow(2, 4) + Prescaler * pow(2, 20) + Autocal * pow(2,21)); // R0 für Startfrequenz ok R[1] = (unsigned long) (1 + Frac1_Int * pow(2, 4)); R[2] = (unsigned long) (2 + mod2 * pow(2, 4) + Frac2_Int * pow(2, 18)); R[3] = (unsigned long) (0x3); R[4] = (unsigned long) (4 + CountRes * pow(2, 4) + CP3_State * pow(2, 5) + Power_Down * pow(2, 6) + PD_Pola * pow(2, 7) + Mux_Log * pow(2, 8) + Ref_Mode * pow(2, 9) + Charge_Pump * pow(2, 10) + Double_Buf * pow(2, 14) + R_Counter * pow(2, 15) + R_Div2 * pow(2, 25) + Ref_Doubler * pow(2, 26) + M_Muxout * pow(2, 27)); R[5] = (unsigned long) (0x800025); R[6] = (unsigned long) (6 + OutPWR * pow(2, 4) + RF_Enable * pow(2, 6) + Reserved * pow(2, 7) + RF_Out_B * pow(2, 10) + MTLD * pow(2, 11) + Reserved * pow(2, 12) + CPBleed * pow(2, 13) + RF_Div_Sel * pow(2, 21) + Feedback * pow(2, 24) +10 * pow(2, 25)); R[7] = (unsigned long) (0x120000E7); R[8] = (unsigned long) (0x102D0428); R[9] = (unsigned long) (0x302FCC9); R[10] = (unsigned long) (10 + (pow(2,22) + pow(2,23) + (ADCClkDiv_Int * pow(2,6)) + pow(2,5) + pow (2,4))); R[11] = (unsigned long) (0x61300B); R[12] = (unsigned long) (0x1041C); if (DEBUG == 1) { /////////////// Serial print values to terminal for diagnostics ///////////// /////////////// if we are in DEBUG mode by setting it to 1 up top ///////////// SerialUSB.print("Reg R0 "); SerialUSB.println(R[0], HEX); SerialUSB.print("Reg R1 "); SerialUSB.println(R[1], HEX); SerialUSB.print("Reg R2 "); SerialUSB.println(R[2], HEX); SerialUSB.print("Reg R3 "); SerialUSB.println(R[3], HEX); SerialUSB.print("Reg R4 "); SerialUSB.println(R[4], HEX); SerialUSB.print("Reg R5 "); SerialUSB.println(R[5], HEX); SerialUSB.print("Reg R6 "); SerialUSB.println(R[6], HEX); SerialUSB.print("Reg R7 "); SerialUSB.println(R[7], HEX); SerialUSB.print("Reg R8 "); SerialUSB.println(R[8], HEX); SerialUSB.print("Reg R9 "); SerialUSB.println(R[9], HEX); SerialUSB.print("Reg R10 "); SerialUSB.println(R[10], HEX); SerialUSB.print("Reg R11 "); SerialUSB.println(R[11], HEX); SerialUSB.print("Reg R12 "); SerialUSB.println(R[12], HEX); SerialUSB.println(" "); SerialUSB.print("Freq = "); SerialUSB.println(TargetFrequency); SerialUSB.print("outdiv = "); SerialUSB.println(outdiv, 20); SerialUSB.print("N = "); SerialUSB.println(N, 20); SerialUSB.print("N_Int = "); SerialUSB.println(N_Int); SerialUSB.print("N - N_Int = "); SerialUSB.println(N - N_Int, 20); SerialUSB.print("Frac1_Int = "); SerialUSB.println(Frac1_Int, DEC); SerialUSB.print("Frac2_Int = "); SerialUSB.println(Frac2_Int, DEC); SerialUSB.print("MOD2 = "); SerialUSB.println(mod2); SerialUSB.println(" "); SerialUSB.print("PDFFreq = "); SerialUSB.println(PFDFreq); SerialUSB.print("ADCClkDiv = "); SerialUSB.println(ADCClkDiv_Int); SerialUSB.print("GCD = "); SerialUSB.println(GCD); } } ///////////////////////// gubbins to output the values to the ADF5355 /////////////////////////// void SetFreq(double TargetFrequency) { static bool FirstTime = true; unsigned long myRegs[13]; // holds the 13 register values if (FirstTime = true) { // this is initialisation as its the first call // convert the frequency into the register values and divide PFD by 2 ConvertFreq(myRegs, TargetFrequency, DIV2ON, AUTOCALON, COUNTERRESETOFF); FirstTime = false; //now send the 13 registers one at a time 0..12 SPI.beginTransaction(MySettings); digitalWrite(CS5355, LOW); for (int registerIndex = 12; registerIndex >= 0; registerIndex--) { // delay before sending register 0 - see datasheet page 33 if (registerIndex == 0) { delayMicroseconds(200); } WriteADFBytes(registerIndex, myRegs); } // convert the frequency into the register values and divide PFD by 0 // only need this next 4 register writes if PFD is > 75MHz ConvertFreq(myRegs, TargetFrequency, DIV2OFF, AUTOCALOFF, COUNTERRESETOFF); WriteADFBytes(4, myRegs); WriteADFBytes(2, myRegs); WriteADFBytes(1, myRegs); WriteADFBytes(0, myRegs); digitalWrite(CS5355, HIGH); SPI.endTransaction(); } else { // convert the frequency into the register values ConvertFreq(myRegs, TargetFrequency, DIV2ON, AUTOCALOFF, COUNTERRESETON); WriteADFBytes(10, myRegs); WriteADFBytes(4, myRegs); WriteADFBytes(2, myRegs); WriteADFBytes(1, myRegs); WriteADFBytes(0, myRegs); ConvertFreq(myRegs, TargetFrequency, DIV2ON, AUTOCALOFF, COUNTERRESETOFF); WriteADFBytes(4, myRegs); delayMicroseconds(200); ConvertFreq(myRegs, TargetFrequency, DIV2ON, AUTOCALON, COUNTERRESETOFF); WriteADFBytes(0, myRegs); ConvertFreq(myRegs, TargetFrequency, DIV2OFF, AUTOCALOFF, COUNTERRESETOFF); WriteADFBytes(4, myRegs); WriteADFBytes(2, myRegs); WriteADFBytes(1, myRegs); WriteADFBytes(0, myRegs); } } void WriteADFBytes(int registerNumber, unsigned long Registers[]) { // make 4 bytes from the register value for SPI-Transfer byte sendByte = 0; for (int i = 3; i >= 0; i--) { sendByte = (byte) ((Registers[registerNumber] >> 8 * i) & 0xFF); SPI.transfer(sendByte); } digitalWrite(CS5355, HIGH); delayMicroseconds(1); digitalWrite(CS5355, LOW); } void setup() { Serial1.begin(9600); // Serial1 is from the Micromite and receives the frequency as a string SPI.begin(); // Initialise the SPI library pinMode(CS5355, OUTPUT); // Initialise the 5355 chip select as high digitalWrite(CS5355, HIGH); // as CS is active low if (DEBUG != 0) { while (!SerialUSB); // no need to init the USB serial but it seems we do need to wait for it to be alive before sending anything } if (DEBUG == 1) { SerialUSB.println("Here we go...."); SerialUSB.println(" "); } // initialise to initFrequency SetFreq(initFrequency); } void loop() { double incomingFrequency = 0.0; // frequency in MHz from MicroMite // only take action if there is data incoming on the serial1 line if (Serial1.available() > 0) { // read the incoming frequency // convert from string to double incomingFrequency = Serial1.readString().toDouble(); // frequency sent is in decimal MHz // if its in range process it if (incomingFrequency >= 54.0 && incomingFrequency <=13600.00) { SetFreq(incomingFrequency); } } }