' ----------------------------------------------------------- ' Program for Micromite to communicate with an ADF4351 based ' 35MHz - 4.4GHz PLL synthesiser module via the SPI interface, ' for checking its capabilities. ' Written by Jim Rowe for Silicon Chip. ' Last revision 29/6/2018 at 10:45 am ' (Corrections advised by Colin Schulz) ' ' Modified 7th December 2019 by G0MGX ' Fixed some highly suspect floating point maths that didnt work ' changed the clock frequency to be 10 MHz ' other giggery pokery and bad magic ' ' NOTE that this program uses an additional Micromite SPI port ' to communicate with the ADF4351 module, using these pins: ' SPI Function Micromite pin ' MOSI (SPI out) Pin 21 ' MISO (SPI in) Pin 22 (not used here) ' SCK (SPI clock) Pin 24 ' LE (load enable) Pin 17 ' LD (lock detect) Pin 18 ' The embedded C function code needed to support this additional ' SPI port can be found at the end of the program. ' ' NOTE also that the SPI interface of the ADF4351 has the ' following requirements: ' 1. The LE line must be held LOW for serial commands on the MOSI ' line to be clocked into the ADF4351, and then pulsed HIGH to ' latch the command into a device register. This must be controlled ' by the program. ' 2. The commands sent to the ADF4351 are all of 32 bits, and must be ' sent MSB first. The three least significant bits of the command ' are the address of the register into which the command is to be ' latched. ' 3. The commands should be sent to the ADF4351 in descending order: ' first ADFReg(5), then ADFReg(4), and so on with ADFReg(0) last. ' 4. Commands are clocked into the ADF4351 on the RISING edges of the ' SCK clock pulses. So for correct operation, the SPI MODE should ' be set for 00 -- i.e., CPOL = 0 and CPHA = 0. ' 5. We are using MMBasic's Speed 0 for the SPI transactions, which ' corresponds to a bit rate/SCK frequency of 1MHz when the Micromite ' CPU clock is 40MHz. ' --------------------------------------------------------------------------- OPTION AUTORUN ON OPTION EXPLICIT DIM AS STRING KPStr$ = " " ' input character from touch screen keypad DIM AS STRING KPInStr$ = " "' input string from keypad (NNNN.NN) DIM AS STRING RFOFreq$ = " "' output frequency display string DIM AS STRING NuFreq$ = " " ' string version of new frequency DIM AS INTEGER index ' counter DIM AS INTEGER outData, inData ' variables DIM AS INTEGER IntFlag = 0 ' flag to show if screen has been touched DIM AS INTEGER SettChg = 0 ' flag to show when settings have changed DIM AS INTEGER Lock = 0 ' PLL lock status (1 = locked) DIM AS INTEGER FPFD = 10 ' effective Ref freq input (D & T=0, R=1) DIM AS INTEGER INTA = 0 DIM AS INTEGER FRAC = 0 ' numerator of the fractional division DIM AS INTEGER MODA = 2 ' preset fractional modulus (2 - 4095) DIM AS INTEGER RFODiv = 1 ' RF output divider value (1 - 64) DIM AS FLOAT INTAF = 0.0 ' integer division factor DIM AS FLOAT FRACF = 0.0 ' FP equivalent of FRAC, used in calc's DIM AS FLOAT OutFreq = 437.00 ' initial value for output frequency DIM AS FLOAT OutRes = 100/1000 ' output frequency resolution 100 KHz Dim AS INTEGER ADFReg(6) ' array for ADF4351 control register commands ADFReg(0) = &HAE8018 ' initial value for ADFReg(0), for startup at 437 MHz ADFReg(1) = &H29 ' initial value for ADFReg(1), with 10MHz clock ADFReg(2) = &H4E42 ' initial value for ADFReg(2) ADFReg(3) = &H4B3 ' initial value for ADFReg(3) ADFReg(4) = &HB5003C ' initial value for ADFReg(4) ADFReg(5) = &H580005 ' initial value for ADFReg(5) ' --------------------------------------------------------------------------- Const DBlue = RGB(0,0,128) CONST Bone = RGB(255,255,192) CONST White = RGB(WHITE) CONST Black = RGB(BLACK) CONST Red = RGB(RED) CONST Green = RGB(GREEN) SETPIN 22, DIN ' declare pin 22 an input (for MISO) PIN(21) = 0 ' then initialise pin 21 to low and SETPIN 21, DOUT ' declare it a digital output (MOSI) PIN(24) = 0 ' next set pin 24 to low and SETPIN 24, DOUT ' declare it a digital output (SCK) PIN(17) = 0 ' then set pin 17 to low and SETPIN 17, DOUT ' declare it a digital output (LE) SETPIN 18, DIN ' declare pin 18 a digital input (LD) PIN(16) = 0 SETPIN 16, DOUT SETPIN 15, INTL, TchInt ' call TchInt to set flag when screen touched TIMER = 0 ' reset timer so it starts ' first write to the ADF4351 registers, to initialise it PRINT "Initialising ADF4351." SendRegData ' send initialising data to ADF4351 registers Wait4Lock ' wait until PLL locked PRINT "Now generating 437.00MHz -- PLL locked!" ' then advise user PRINT " " ' ' ----------------------------------------------------------------- ' main program loop starts here ShowMainScrn ' first show the main screen DO IF IntFlag = 1 THEN CheckBtn ' if screen was touched, go check it out IF SettChg = 1 THEN ' if settings were changed PIN(16) = 0 UpdateRegs ' calculate the new register values SendRegData ' then send to the ADF4351 Wait4Lock ' wait until it locks, then RFOFreq$ = STR$(OutFreq,0,2) + " MHz" ShowMainScrn ' show main screen with new frequency print ("Output Frequency " + RFOFreq$ + ", Lock = " + STR$(Lock)) PRINT " " END IF SettChg = 0 ' finally clear settings changed flag PAUSE 500 ' set to loop about twice per second LOOP END ' end of main part of program; subroutines and functions follow ' ----------------------------------------------------------------- ' subroutine to show the main LCD screen with buttons SUB ShowMainScrn IntFlag = 0 ' clear interrupt flag to begin CLS Black RFOFreq$ = STR$(OutFreq,0,2) + " MHz" RBOX 0,0, MM.HRes-2, MM.VRes-2, 5, RGB(Cyan), DBlue TEXT MM.HRes/2, MM.VRes/8, " G0MGX ", CM, 1, 3, Red, DBlue TEXT MM.HRes/2, MM.VRes/4, "ADF4351 Module Driver Program", CM, 1, 1, White, DBlue TEXT MM.HRes/2, MM.VRes*3/8, "Current Frequency:", CM, 1, 2, Green, DBlue RBOX 4, MM.VRes/2, MM.HRes-10, MM.VRes/4-4, 8, Black, Red TEXT MM.HRes/2, MM.VRes*5/8, RFOFreq$, CM, 1, 3, White, Red RBOX 4, MM.VRes*3/4, MM.HRes-10, MM.VRes/4-6, 8, Black, Bone TEXT MM.HRes/2, MM.VRes*7/8, "TOUCH TO CHANGE", CM, 1, 2, Black, Bone END SUB ' ----------------------------------------------------------------- ' subroutine to set IntFlag when touchscreen is touched SUB TchInt IntFlag = 1 END SUB ' ----------------------------------------------------------------- ' subroutine to wait until PLL is in Locked state (pin 18 = 1) SUB Wait4Lock PRINT "Waiting for lock" LChk: IF (PIN(18) <> 1) THEN GOTO LChk ENDIF Lock = 1 PIN(16) = 1 print "Lock Complete" END SUB ' ----------------------------------------------------------------- ' subroutine to send command words to the six ADF4351 registers, ' in descending order as recommended SUB SendRegData For index = 5 To 0 STEP -1 outData = ADFReg(index) ' make outData the next 32-bit word to send Send32 ' then send it Print ("Reg(" + STR$(index) + ") = " + HEX$(outData, 8)) Next END SUB ' ----------------------------------------------------------------- ' subroutine to send a 32-bit word to the ADF4351 SUB Send32 PIN(17) = 0 ' first bring LE low to start transaction inData = SPIPort(22,21,24,outData,0,0,32) ' then send the 32-bit data PIN(17) = 1 ' then bring LE high to load it into register PAUSE 0.1 ' pause for 100us make sure data is latched PIN(17) = 0 ' then bring it low again END SUB ' and return ' ----------------------------------------------------------------- ' subroutine called when screen is touched, to check whether it has ' been touched on the 'TOUCH TO CHANGE' button at the bottom of ' the LCD screen (or not, in which case it just returns) SUB CheckBtn IntFlag = 0 ' first clear interrupt flag IF TOUCH(Y) < MM.VRes*3/4 THEN SettChg = 0 ' no valid button pressed, so just return EXIT SUB ' with SettChg still 0 ELSE ' must have touched button, so ChangeFreq ' go allow input of a new frequency SettChg = 1 ' and set flag to show a change was made END IF END SUB ' before returning ' ----------------------------------------------------------------- ' subroutine to change the ADF4351 output frequency SUB ChangeFreq IntFlag = 0 ' clear interrupt flag to begin CLS Black TEXT 0,MM.VRes/16, "Current Frequency:", LM,1,1, White, Black TEXT MM.HRes/4, MM.VRes*3/16, STR$(OutFreq,0,2), CM, 1,2, White, Black TEXT 0, MM.VRes*5/16, "Enter New Freq:", LM, 1,1, White, Black BOX 0, MM.VRes*3/8, MM.HRes/2 -4, MM.VRes/8, 0, White, White TEXT MM.HRes/4, MM.VRes*7/16, SPACE$(7), CM, 1,2, Red, White TEXT 0, MM.VRes*5/8, "TOUCH OK BUTTON",LM,1,1, Green, Black TEXT 0, MM.VRes*11/16, "BELOW TO ENTER:", LM,1,1,Green, Black KPadDraw ' now go draw keypad KPInStr$ = SPACE$(7) ' clear the input string DO InCharFrmKP ' then go get a character/string from the keypad (> KPStr$) SELECT CASE KPStr$ CASE "OK" ' OK button was touched IF KPInStr$ = SPACE$(7) THEN EXIT DO ' if KPInStr$ null string, exit now NuFreq$ = LEFT$(KPInStr$, 7) ' otherwise make KPInStr$ the new Frequency IF Val(NuFreq$) < 35 OR VAL(NuFreq$) > 4400 THEN EXIT DO ' bomb out if outside range TEXT MM.HRes/4, MM.VRes*7/16, NuFreq$, CM, 1,2, Red, White ' show it OutFreq = VAL(NuFreq$) ' then update output frequency EXIT DO ' and leave CASE "0" TO "9", "-", "." KPInStr$ = RIGHT$(KPInStr$, 6) ' a numeral, so shorten KPInStr$ KPInStr$ = KPInStr$ + KPStr$ ' then add the new digit to it TEXT MM.HRes/4, MM.VRes*7/16, KPInStr$, CM, 1,2, Red, White ' & show it CASE CHR$(08) KPInStr$ = Left$(KPInStr$, LEN(KPInStr$) -1) 'if it's a backspace, blank last char TEXT MM.HRes/4, MM.VRes*7/16, KPInStr$ + " ", CM, 1,2, Red, White '& re-show END SELECT PAUSE 250 LOOP END SUB ' ----------------------------------------------------------------- ' subroutine to draw keypad on right, OK & backspace buttons lower left SUB KPadDraw ' keypad boxes RBOX MM.HRes/2, 0, MM.HRes/6-4, MM.VRes/4-4, 10, Black, Bone RBOX MM.HRes*4/6, 0, MM.HRes/6-4, MM.VRes/4-4, 10, Black, Bone RBOX MM.HRes*5/6, 0, MM.HRes/6-4, MM.VRes/4-4, 10, Black, Bone RBOX MM.HRes/2, MM.VRes/4, MM.HRes/6-4, MM.VRes/4-4, 10, Black, Bone RBOX MM.HRes*4/6, MM.VRes/4, MM.HRes/6-4, MM.VRes/4-4, 10, Black, Bone RBOX MM.HRes*5/6, MM.VRes/4, MM.HRes/6-4, MM.VRes/4-4, 10, Black, Bone RBOX MM.HRes/2, MM.VRes/2, MM.HRes/6-4, MM.VRes/4-4, 10, Black, Bone RBOX MM.HRes*4/6, MM.VRes/2, MM.HRes/6-4, MM.VRes/4-4, 10, Black, Bone RBOX MM.HRes*5/6, MM.VRes/2, MM.HRes/6-4, MM.VRes/4-4, 10, Black, Bone RBOX MM.HRes/2, MM.VRes*3/4, MM.HRes/6-4, MM.VRes/4-4, 10, Black, Bone RBOX MM.HRes*4/6, MM.VRes*3/4, MM.HRes/6-4, MM.VRes/4-4, 10, Black, Bone RBOX MM.HRes*5/6, MM.VRes*3/4, MM.HRes/6-4, MM.VRes/4-4, 10, Black, Bone ' boxes at lower left for backspace and OK buttons, plus legends RBOX 0, MM.VRes*3/4, MM.HRes/6-4, MM.VRes/4, 10, Black, Bone RBOX MM.HRes/6, MM.VRes*3/4, MM.HRes/3-4, MM.VRes/4, 10, Black, Bone TEXT MM.HRes/12, MM.VRes*7/8, "<-", CM, 1, 2, Black, Bone TEXT MM.HRes/3, MM.VRes*7/8, "OK", CM, 1, 3, Red, Bone ' legends for the keypad buttons TEXT MM.HRes*7/12, MM.VRes/8, "7", CM, 1, 3, Black, Bone TEXT MM.HRes*9/12, MM.VRes/8, "8", CM, 1, 3, Black, Bone TEXT MM.HRes*11/12, MM.VRes/8, "9", CM, 1, 3, Black, Bone TEXT MM.HRes*7/12, MM.VRes*3/8, "4", CM, 1, 3, Black, Bone TEXT MM.HRes*9/12, MM.VRes*3/8, "5", CM, 1, 3, Black, Bone TEXT MM.HRes*11/12, MM.VRes*3/8, "6", CM, 1, 3, Black, Bone TEXT MM.HRes*7/12, MM.VRes*5/8, "1", CM, 1, 3, Black, Bone TEXT MM.HRes*9/12, MM.VRes*5/8, "2", CM, 1, 3, Black, Bone TEXT MM.HRes*11/12, MM.VRes*5/8, "3", CM, 1, 3, Black, Bone TEXT MM.HRes*7/12, MM.VRes*7/8, "-", CM, 1, 3, Black, Bone TEXT MM.HRes*9/12, MM.VRes*7/8, "0", CM, 1, 3, Black, Bone TEXT MM.HRes*11/12, MM.VRes*7/8, ".", CM, 1, 3, Black, Bone END SUB ' -------------------------------------------------------------------- SUB InCharFrmKP ' sub to get a char from the touch keypad ' (returns char in KPStr$) IntFlag = 0 ' clear interrupt flag to begin ICFK2: IF TOUCH(X) = -1 THEN GOTO ICFK2 'wait until keypad touched ' but also loop back if no valid button was touched IF TOUCH(X) < MM.HRes/2 AND TOUCH(Y) < MM.VRes*3/4 THEN GOTO ICFK2 SELECT CASE TOUCH(X) ' now decide which button was touched CASE 0 TO MM.HRes/6-1 KPStr$ = CHR$(08) ' must have been backspace button CASE MM.HRes/6 TO MM.HRes/2-1 KPStr$ = "OK" ' must have been OK button CASE MM.HRes/2 TO MM.HRes*2/3-1 'first column of keypad itself SELECT CASE TOUCH(Y) ' must be first column CASE 0 TO MM.VRes/4 -1 ' if it's in the first row KPStr$ = "7" ' must be the 7 button CASE MM.VRes/4 TO MM.VRes/2 -1 'try second row KPStr$ = "4" ' must be the 4 button CASE MM.VRes/2 TO MM.Vres*3/4 -1 'try third row KPStr$ = "1" ' must be the 1 button CASE MM.VRes*3/4 TO MM.VRes ' try fourth row KPStr$ = "-" ' must be the hyphen button END SELECT CASE MM.HRes*2/3 TO MM.HRes*5/6-1 'centre column of keypad SELECT CASE TOUCH(Y) CASE 0 TO MM.VRes/4 -1 ' if it's in the first row KPStr$ = "8" ' must be the 8 button CASE MM.VRes/4 TO MM.VRes/2 -1 'try second row KPStr$ = "5" ' must be the 5 button CASE MM.VRes/2 TO MM.Vres*3/4 -1 'try third row KPStr$ = "2" ' must be the 2 button CASE MM.VRes*3/4 TO MM.VRes ' try fourth row KPStr$ = "0" ' must be the 0 button END SELECT CASE MM.HRes*5/6 TO MM.HRes ' last column of keypad SELECT CASE TOUCH(Y) CASE 0 TO MM.VRes/4 -1 ' if it's in the first row KPStr$ = "9" ' must be the 9 button CASE MM.VRes/4 TO MM.VRes/2 -1 'try second row KPStr$ = "6" ' must be the 6 button CASE MM.VRes/2 TO MM.Vres*3/4 -1 'try third row KPStr$ = "3" ' must be the 3 button CASE MM.VRes*3/4 TO MM.VRes ' try fourth row KPStr$ = "." ' must be the DP button END SELECT END SELECT END SUB FUNCTION CALC_GCD (A, B) AS INTEGER LOCAL T AS INTEGER DO WHILE B <> 0 T = B B = A MOD B A = T LOOP CALC_GCD = A END FUNCTION ' ----------------------------------------------------------------- ' subroutine to calculate ADF4351 register settings for a new ' frequency setting (= OutFreq) SUB UpdateRegs LOCAL AS INTEGER DIVISOR IF OutFreq >= 2200 THEN RFODiv = 1 ADFReg(4) = &H85003C ENDIF if OutFreq < 2200 THEN RFODiv = 2 ADFReg(4) = &H95003C ENDIF IF OutFreq < 1100 THEN RFODiv = 4 ADFReg(4) = &HA5003C ENDIF IF OutFreq < 550 THEN RFODiv = 8 ADFReg(4) = &HB5003C ENDIF IF OutFreq < 275 THEN RFODiv = 16 ADFReg(4) = &HC5003C ENDIF IF OutFreq < 137.5 THEN RFODiv = 32 ADFReg(4) = &HD5003C ENDIF IF OutFreq < 68.75 THEN RFODiv = 64 ADFReg(4) = &HE5003C ENDIF ' Apply the G0MGX method of maths ' some very dodgy maths in the original which had rounding/floating point issues INTA = FIX(((OutFreq * RFODiv) / FPFD)) MODA = FPFD / OutRes FRACF = OutFreq * RFODiv FRACF = FRACF MOD FPFD FRACF = (FRACF * MODA) / 10 FRAC = FIX(FRACF) ' calculate GCD DIVISOR = CALC_GCD (FRAC, MODA) ' USE GCD to simplify values FRAC = FRAC / DIVISOR MODA = MODA / DIVISOR ' smallest MOD value is 2 so round back up if necessary IF MODA = 1 THEN MODA = 2 ENDIF ' now set ADFReg(0) ADFReg(0) = 0 ' first clear it ADFReg(0) = (INTA << 15) ' then slot INTA into bits 15-30 ADFReg(0) = ADFReg(0) + (FRAC << 3) ' and add in FRAC to bits 3 - 14 ' also set ADFReg(1) ADFReg(1) = &H00000000 ' set to 0 ADFReg(1) = ADFReg(1) + (MODA << 3) ' slot MODA into bits 3 - 14 ADFReg(1) = ADFReg(1) + 1 ' add 1 for reg addr ADFReg(1) = ADFReg(1) + (2 ^ 27) ' set bit 27 ADFReg(1) = ADFReg(1) + (2 ^ 15) ' set bit 15 ' then set ADFReg(2), ADFReg(3) and ADFReg(5) ADFReg(2) = &H00005E42 ' MUX output ADFReg(3) = &H4B3 ' PD polarity positive, clk divider off ADFReg(5) = &H580005 ' LD pin set for dig lock detect END SUB ' ----------------------------------------------------------------- ' SPI Port function (Revised by Geoff Graham, 4/8/2017) CFunction SPIPort(integer, integer, integer, integer) integer 00000008 40024800 00442021 40024800 0044102B 1440FFFD 00000000 03E00008 00000000 27BDFFB0 AFBF004C AFBE0048 AFB70044 AFB60040 AFB5003C AFB40038 AFB30034 AFB20030 AFB1002C AFB00028 00808821 00A09021 00C0A021 00E0A821 10800005 8FB30068 10A00004 3C029D00 14C00008 3C109D00 3C029D00 8C420010 00002021 24050002 0040F809 00003021 3C109D00 8E020010 8E240000 24050002 0040F809 00003021 8E020024 8E240000 0040F809 00002821 AFA20018 8E020028 0040F809 8E240000 AFA2001C 8E020024 8E440000 0040F809 24050006 AFA20020 8E020024 8E440000 0040F809 24050005 AFA20024 8E020028 0040F809 8E440000 24170001 0057B804 8FA20064 10400008 3C109D00 8C420000 50400006 8E020024 24030003 5443000D 8E020024 3C109D00 8E020024 8E840000 0040F809 24050005 0040B021 8E020024 8E840000 0040F809 24050006 1000000A 0040F021 8E840000 0040F809 24050006 0040B021 8E020024 8E840000 0040F809 24050005 0040F021 3C029D00 8C420028 0040F809 8E840000 24140001 0054A004 12600002 24110008 8E710000 2631FFFF 32220020 24030001 02238804 02208021 0002800A 0002880B 2402FFFF 2403FFFF AFA20010 12A00005 AFA30014 8EA20000 8EA30004 AFA20010 AFA30014 8FA30060 10600002 0000A821 8C750000 02301025 00009021 10400039 00009821 8FA40010 02241824 8FA60014 02061024 00621025 10400004 8FA30024 8FA20020 10000002 AC570000 AC770000 AED40000 00000000 00000000 00000000 00000000 00000000 00000000 52A00013 00121FC2 02A02021 0411FF6A 00000000 00121FC2 00131040 00122840 8FA60018 8CC40000 8FA6001C 00C42006 30840001 00A49025 00629825 AFD40000 0411FF5D 02A02021 1000000C 00101FC0 00131040 00122840 8FA60018 8CC40000 8FA6001C 00C42006 30840001 00A49025 00629825 AFD40000 00101FC0 00111042 00621025 00101842 00408821 00431025 1440FFC9 00608021 00000000 00000000 00000000 00000000 8FA20064 10400006 00000000 8C420000 10400003 24030002 14430003 02401021 AED40000 02401021 02601821 8FBF004C 8FBE0048 8FB70044 8FB60040 8FB5003C 8FB40038 8FB30034 8FB20030 8FB1002C 8FB00028 03E00008 27BD0050 End CFunction