$regfile = "m8def.dat" $crystal = 16000000 '20070127 v00 skeleton test lcd and i2c led flash '20070128 v01 basic dds calculation (clumsy!) and display/button control '20070128 v02 dds clock set in menu '20070130 v03 dds clock multiplier in menu '20070131 v04 finishing touches '20070201 v05 documented '20070206 v06 choose between pcf8574 and pcf8574a i/o expander '20070212 v07 changed i2c pinning '20070213 v08 corrected error in ddsclock frequency menu setting '20070221 v09 internal pull-up on pcf8574type pin '20070318 v10 loop-led on portd7.7 '20070328 v11 longer buttons debounce time (20->40msec) '20070401 v12 buttons now read out in polling mode directly on portd '-------- update version lcd! ' 'Charlos Potma, PA3CKR 'Bascom program to control an AD9951 DDS. 'An I2C I/O port expander IC type PCF8574A is used to read eight buttons: 'button 1 increases dds output frequency with step value 'button 2 decreases dds output frequency with step value 'button 3 increases frequency stepvalue (1Hz..10MHz) 'button 4 decreases frequency stepvalue (1Hz..10MHz) 'button 5 cycles through 10 frequency memories 'button 6 stores the dds frequency in the current memory 'button 7 recalls the frequency from the current memory 'button 8 starts a simple menu: '-first, a dds reference clock multiplier can be chosen: either 1 (no multiplication) ' or a multiplier in the range 4..20 '-second, the basic dds refence clock frequency (before multiplication) ' can be changed using button 1..4 ' the resulting multiplier and dds basic reference clock frequency are stored in eeprom 'When changing the dds basic reference clock frequency, please note that you must 'keep in mind that the resulting dds reference clock is kept within the AD9951 'limits. The program does not do that to allow you a fair amount of overclocking! 'My AD9951 was ok with a 50MHz basic reference and a multiplier of 15, 'resulting in a reference clock of 750MHz... 'Note that this program sets the vco range control bit if reference clock 'is higher than 250MHz. ' 'When the ATMega8 is programmed, the eeprom is probably empty, the AD9951 will 'generate frequency 0Hz. 'Then switch on the ATMega8 while one of the buttons is pressed, 'until the LCD reports : "Deflts restored ". 'The default list is shown below in the source, see 'Write Default Data To Eeprom ' 'Config LCD Config Lcd = 16 * 2 Config Lcdpin = Pin , Db4 = Portb.0 , Db5 = Portb.1 , Db6 = Portb.2 , Db7 = Portb.3 , E = Portb.4 , Rs = Portb.5 Config Portd = Input Config Pinc.0 = Output Config Pinc.1 = Output Config Pinc.2 = Output Config Pinc.3 = Output Config Pinc.5 = Output Ad9951reset Alias Portc.0 Ad9951sclk Alias Portc.1 Ad9951sdio Alias Portc.2 Ad9951ioupdate Alias Portc.3 Led Alias Portc.5 'AD9951 ftw0 register write address Const Ad9951ftw0 = &B0000100 'AD9951 control function register number 2 address Const Ad9951cfr2 = &B00000001 'AD9951 ref clock multiplier low limit Const Ad9951rcmlow = 4 'AD9951 ref clock multiplier high limit Const Ad9951rcmhigh = 20 'AD9951 vco range control trip (sets bit 2 in cfr2 when ddscock > 250MHz) Const Ad9951vcohigh = 250000000 'Buttons on Portd 'Frequency up button is on bit0 of Portd Const Frequp = &B11111110 'Frequency down button is on bit1 of Portd Const Freqdn = &B11111101 'Step up button is on bit2 of Portd Const Stepup = &B11111011 'Step down button is on bit3 of Portd Const Stepdn = &B11110111 'Memory button is on bit4 of Portd Const Mem = &B11101111 'Memory store button is on bit5 of Portd Const Memstr = &B11011111 'Memory recall button is on bit6 of Portd Const Memrcl = &B10111111 'Menu button is on bit7 of Portd Const Menu = &B01111111 'Number of frequency memories Const Freqmemmax = 10 'Number of frequency stepvalues in data block (minus 1) Const Fstepsnum = 7 'Default Fsteps data table index Const Fstepsdefault = 1 'constant used in dds frequency calculation '2^31 Const Twop31 = 2147483648 '2^9 Const Twop9 = 512 '2^8 Const Twop8 = 256 'AD9951 register address Dim Ddsregaddress As Byte 'temporary storage Dim Tmpword As Word Dim Tmpint As Integer '1 if there is work to do in main loop Dim Doflag As Bit 'last memory place used Dim Memdispflag As Bit '1 if menu button has been pressed Dim Menuflag As Bit 'multiplier entry flag Dim Multflag As Bit 'last frequency memory number used Dim Lastmemnum As Byte 'AD9951 ref clock multiplier temporary Dim Ddsrcmtmp As Byte 'AD9951 ref clock multiplier Dim Ddsrcm As Byte 'AD9951 dds output frequency Dim Ddsfrequency As Long 'AD9951 dds output frequency as double Dim Ddsfrequencydbl As Double 'AD9951 temporary dds output frequency Dim Tmpddsfrequency As Long 'index in frequency steps array Dim Ddsfreqstepindex As Byte 'Frequency step value Dim Ddsfreqstep As Long 'frequency step value exponent Dim Ddsfsexp As Integer 'high limit dds frequency as long Dim Ddshighlng As Long 'low limit dds frequency as long Dim Ddslowlng As Long 'AD9951 basic clock frequency (external clock frequency) Dim Ddsclockbasic As Long 'AD9951 clock frequency (after multiplication with ref clock multiplier) Dim Ddsclock As Long 'AD9951 clock frequency as double Dim Ddsclockdbl As Double 'AD9951 tuning word as a double Dim Ftwdbl As Double 'AD9951 tuning word as single Dim Ftwsgl As Single 'AD9951 tuning word as long Dim Ftwlng As Long 'constant used in tuning word calculation Dim Cftwdbl As Double 'strings used in formatting frequency info for lc display Dim Tmpstr As String * 12 Dim Tmpddsfreqstr As String * 12 Dim Ddsfreqstr As String * 12 'cursor blink position to indicate fstep value Dim Blinkposition As Byte 'used in strin manipulations Dim Strlen As Integer 'holds button bit pattern Dim Buttons As Byte 'do not use first eeprom byte, could be trashed during a reset Dim Eedummy As Eram Byte 'last frequency saved Dim Eelastmemnum As Eram Byte 'last frequency step used Dim Eelastfreqstepindex As Eram Byte 'frequency memories Dim Eefrequencies(freqmemmax) As Eram Long 'current AD9951 basic clock frequency Dim Eeddsclock As Eram Long 'ref clock multiplier Dim Eeddsrcm As Eram Byte 'current constant used in dds tuning word calculation 'Dim Eecftwdbl As Eram Long 'sub to handle buttons Declare Sub Handlebuttons 'setup AD9951 with ref clock multiplier Declare Sub Setad9951mult 'sub to program AD9951 Declare Sub Progad9951 'sub to calculate constant used in tuning word calculation Declare Sub Calccftwconstant 'sub to calculate dds frequency tuning word Declare Sub Calccftw 'sub to update lcdisplay, calc ftw Declare Sub Updddsdisp 'sub to reset AD9951 Declare Sub Resetad9951 'calculate frequency step value and cursor position Declare Sub Calcstpexp 'make frequency display string Declare Sub Calcfreqstr 'enable weak pull-up on portd Portd = 255 'clear lcd, report version Cls Cursor Off Lcd "AD9951 v12" Locate 2 , 1 Lcd "PA3CKR" Wait 1 Cls ''If any of the buttons is pressed at power-up, restore memory defaults If Pind <> 255 Then 'Write Default Data To Eeprom Eelastmemnum = 1 Eelastfreqstepindex = 1 Eefrequencies(1) = 10000000 Eefrequencies(2) = 11000000 Eefrequencies(3) = 12000000 Eefrequencies(4) = 13000000 Eefrequencies(5) = 14000000 Eefrequencies(6) = 15000000 Eefrequencies(7) = 16000000 Eefrequencies(8) = 17000000 Eefrequencies(9) = 18000000 Eefrequencies(10) = 19000000 Eeddsclock = 50000000 Eeddsrcm = 1 Cls Lcd "Deflts restored " Wait 3 End If Cls Lcd "Resetting AD9951" Call Resetad9951 Wait 1 Lastmemnum = Eelastmemnum Ddsfrequency = Eefrequencies(lastmemnum) Ddsfreqstepindex = Eelastfreqstepindex 'calculate frequency step value and cursor position Call Calcstpexp Ddsclockbasic = Eeddsclock Ddsrcm = Eeddsrcm Ddsclock = Ddsclockbasic * Ddsrcm Cls Lcd "Bclck: " ; Ddsclockbasic Locate 2 , 1 Lcd "Rcm : " ; Ddsrcm Wait 3 'set dds low limit to 100kHz Ddslowlng = 100000 'set dds high limit to 40% of ddsclock Ddshighlng = Ddsclock / 10 Ddshighlng = Ddshighlng * 4 Reset Doflag Reset Menuflag Reset Memdispflag Call Updddsdisp Call Calccftwconstant Call Calccftw Call Setad9951mult Call Progad9951 'main loop starts here Do 'show we are alive... Toggle Led 'read buttons Buttons = Pind If Buttons <> 255 Then 'yes, a button was pressed Set Doflag Waitms 200 End If If Doflag = 1 Then Reset Doflag Call Handlebuttons 'update lc display Call Updddsdisp 'calculate new tuning word Call Calccftw End If 'program multiplier Call Setad9951mult 'program AD9951 frequency Call Progad9951 'Locate 2 , 1 'Lcd Ddsrcm ; " " ; Ftwlng Waitms 10 Loop '#############################subroutines follow################################ Sub Handlebuttons 'Frequency up button If Buttons = Frequp Then Reset Memdispflag If Menuflag = 1 Then 'increase dds clock frequency Ddsclockbasic = Ddsclockbasic + Ddsfreqstep Ddsclock = Ddsclockbasic * Ddsrcm 'set dds high limit to 40% of ddsclock Ddshighlng = Ddsclock / 10 Ddshighlng = Ddshighlng * 4 Call Calccftwconstant Elseif Multflag = 1 Then If Ddsrcm = 1 Then Ddsrcm = 4 Else Ddsrcmtmp = Ddsrcm + 1 If Ddsrcmtmp <= Ad9951rcmhigh Then 'increase multiplier Ddsrcm = Ddsrcmtmp End If End If Call Setad9951mult Ddsclockbasic = Eeddsclock Ddsclock = Ddsclockbasic * Ddsrcm 'set dds high limit to 40% of ddsclock Ddshighlng = Ddsclock / 10 Ddshighlng = Ddshighlng * 4 Call Calccftwconstant Else 'increase dds frequency Tmpddsfrequency = Ddsfrequency + Ddsfreqstep If Tmpddsfrequency <= Ddshighlng Then Ddsfrequency = Tmpddsfrequency End If End If End If 'Frequency down button If Buttons = Freqdn Then Reset Memdispflag If Menuflag = 1 Then 'decrease dds clock frequency Ddsclockbasic = Ddsclockbasic - Ddsfreqstep Ddsclock = Ddsclockbasic * Ddsrcm 'set dds high limit to 40% of ddsclock Ddshighlng = Ddsclock / 10 Ddshighlng = Ddshighlng * 4 Call Calccftwconstant Elseif Multflag = 1 Then If Ddsrcm = 4 Then Ddsrcm = 1 Else Ddsrcmtmp = Ddsrcm - 1 If Ddsrcmtmp >= Ad9951rcmlow Then 'decrease multiplier Ddsrcm = Ddsrcmtmp End If End If Call Setad9951mult Ddsclockbasic = Eeddsclock Ddsclock = Ddsclockbasic * Ddsrcm 'set dds high limit to 40% of ddsclock Ddshighlng = Ddsclock / 10 Ddshighlng = Ddshighlng * 4 Call Calccftwconstant Else 'decrease dds frequency Tmpddsfrequency = Ddsfrequency - Ddsfreqstep If Tmpddsfrequency >= Ddslowlng Then Ddsfrequency = Tmpddsfrequency End If End If End If 'Frequency stepvalue up button If Buttons = Stepup Then Reset Memdispflag Reset Multflag 'increase step frequency If Ddsfreqstepindex < Fstepsnum Then Ddsfreqstepindex = Ddsfreqstepindex + 1 Call Calcstpexp End If End If 'Frequency stepvalue down button If Buttons = Stepdn Then Reset Memdispflag Reset Multflag 'decrease step frequency If Ddsfreqstepindex > 0 Then Ddsfreqstepindex = Ddsfreqstepindex - 1 Call Calcstpexp End If End If 'Memory button (do only if not in menu) If Buttons = Mem Then Reset Multflag If Menuflag = 1 Then 'we were in menu mode, jump out Ddsclock = Eeddsclock Reset Menuflag Else If Memdispflag = 0 Then Set Memdispflag Else 'cycle through memories If Lastmemnum = Freqmemmax Then Lastmemnum = 1 Else Lastmemnum = Lastmemnum + 1 End If Set Memdispflag End If End If End If 'Store button If Buttons = Memstr Then If Menuflag = 1 Then 'we were in menu mode, jump out Ddsclock = Eeddsclock Reset Menuflag Else 'store frequency in current memory Eelastmemnum = Lastmemnum Eefrequencies(lastmemnum) = Ddsfrequency Cls Locate 1 , 1 Lcd "Stored" Reset Memdispflag Wait 1 End If End If 'Recall button If Buttons = Memrcl Then If Menuflag = 1 Then 'we were in menu mode, jump out Ddsclock = Eeddsclock Reset Menuflag Else 'recall frequency from current memory Eelastmemnum = Lastmemnum Ddsfrequency = Eefrequencies(lastmemnum) Call Progad9951 Cls Locate 1 , 1 Lcd "Recalled" Reset Memdispflag Wait 1 End If End If 'Menu button If Buttons = Menu Then 'always reset memory mode Reset Memdispflag If Multflag = 1 Then Reset Multflag Set Menuflag Else If Menuflag = 1 Then 'we were in menu mode, save clock frequency and exit Reset Menuflag 'store new ref clock frequency and multiplier Eeddsclock = Ddsclockbasic Eeddsrcm = Ddsrcm Cls Lcd "Store dds clock" Locate 2 , 1 Lcd "Store dds rcm" Wait 1 Else 'we were in normal mode, set menu flag Set Multflag End If End If End If End Sub '############################################################################### Sub Setad9951mult 'send ref clock multiplier to the ad9951 'put multiplier in bits 7..3 of byte Ddsrcmtmp = Ddsrcm Shift Ddsrcmtmp , Left , 3 If Ddsclock > Ad9951vcohigh Then 'set vco bit for high clock speed Ddsrcmtmp = Ddsrcmtmp + 4 End If 'shift cfrd0 register address out, data on rising edge of clock Ddsregaddress = Ad9951cfr2 Shiftout Ad9951sdio , Ad9951sclk , Ddsregaddress , 1 , 8 'shift two zero bytes Ddsregaddress = 0 Shiftout Ad9951sdio , Ad9951sclk , Ddsregaddress , 1 , 8 Shiftout Ad9951sdio , Ad9951sclk , Ddsregaddress , 1 , 8 Shiftout Ad9951sdio , Ad9951sclk , Ddsrcmtmp , 1 , 8 'Toggle Ioupdate Pin Set Ad9951ioupdate Waitus 20 Reset Ad9951ioupdate End Sub '############################################################################### Sub Progad9951 'send control byte and four bytes of the tuning word to the ad9951 'shift ftw0 register address out, data on rising edge of clock Ddsregaddress = Ad9951ftw0 Shiftout Ad9951sdio , Ad9951sclk , Ddsregaddress , 1 , 8 'shift tuning word out, data on rising edge of clock 'Ftwlng = &H051EB851 Shiftout Ad9951sdio , Ad9951sclk , Ftwlng , 1 , 32 'Toggle Ioupdate Pin Set Ad9951ioupdate Waitus 20 Reset Ad9951ioupdate End Sub '############################################################################### Sub Calccftwconstant 'calculate constant used in tuning word calculation 'this calculation is rather clumsy because bascom does not have 'unsigned long and unsigned longlong data types 'first fill cftw with 2^31 (cannot use 2^32 as the leftmost bit is used 'as sign bit Cftwdbl = Twop31 'divide this value with current dds clock frequency Ddsclockdbl = Ddsclock Cftwdbl = Cftwdbl / Ddsclockdbl 'now the result is small enough to multiply with 2^9 and avoid overflow 'multiply by 2^9 to get 2^40 (why 2^40 ??? see sub calccftw) Cftwdbl = Cftwdbl * Twop9 End Sub '############################################################################### Sub Calccftw 'calculate actual dds frequency tuning word as a four byte integer 'first, multiply the actaul dds frequency with the constant calculated in Calccftwconstant Ddsfrequencydbl = Ddsfrequency Ftwdbl = Ddsfrequencydbl * Cftwdbl 'shift the result right eight bits to correct for the fact that cftw was 'calculated with effectively 2^40. This value is used so as not to lose 'accuray in the resulting dds frequency tuning word Ftwdbl = Ftwdbl / Twop8 'store the result in a single. We now lose accuracy!, 'this is now used instead of a yet to be written procedure to get the correct 'integer value from a double. Ftwsgl = Ftwdbl 'and get the integer value Ftwlng = Int(ftwsgl) 'Cls 'Lcd "f" ; Ftwlng 'Wait 1 End Sub '############################################################################### Sub Updddsdisp 'update lcdisplay 'display current dds frequency formatted as: xxx,xxx.xxx If Multflag = 1 Then Cls Cursor Off Lcd "Multiplier : " ; Ddsrcm Locate 2 , 1 Lcd "Set mult,mnu=nxt" Elseif Menuflag = 0 Then Tmpddsfreqstr = Str(ddsfrequency) Call Calcfreqstr Locate 1 , 1 Lcd Ddsfreqstr ; "MHz " Locate 2 , 1 Lcd " " If Memdispflag = 1 Then Tmpddsfrequency = Eefrequencies(lastmemnum) Tmpddsfreqstr = Str(tmpddsfrequency) Call Calcfreqstr Locate 2 , 1 Lcd Ddsfreqstr Lcd " M" ; Lastmemnum End If Else 'menuflag is set Call Calccftwconstant Tmpddsfrequency = Ddsclockbasic Tmpddsfreqstr = Str(tmpddsfrequency) Call Calcfreqstr Cls Lcd Ddsfreqstr Locate 2 , 1 Lcd "Set clk,mnu=exit" End If Locate 1 , Blinkposition Cursor On , Noblink End Sub '############################################################################### Sub Resetad9951 'reset ad9951 several times For Tmpint = 1 To 5 Set Ad9951reset Waitms 200 Reset Ad9951reset Next Tmpint End Sub '############################################################################### Sub Calcstpexp 'sub to calculate 10^fstepindex Blinkposition = Lookup(ddsfreqstepindex , Fblink) Ddsfsexp = Lookup(ddsfreqstepindex , Fsteps) Ddsfreqstep = 1 If Ddsfsexp > 0 Then For Tmpint = 1 To Ddsfsexp Ddsfreqstep = 10 * Ddsfreqstep Next Tmpint End If End Sub '############################################################################### Sub Calcfreqstr 'sub to make dds frequency string for display Ddsfreqstr = " 0, . " Strlen = Len(tmpddsfreqstr) 'Cls 'Lcd "strlen:" ; " " ; Strlen 'Wait 2 Strlen = Strlen - 2 Tmpstr = Mid(tmpddsfreqstr , Strlen , 3) Mid(ddsfreqstr , 9 , 3) = Tmpstr Strlen = Strlen - 3 Tmpstr = Mid(tmpddsfreqstr , Strlen , 3) Mid(ddsfreqstr , 5 , 3) = Tmpstr Strlen = Strlen - 3 Tmpint = -2 If Strlen > -2 Then Strlen = Strlen + 2 Tmpstr = Left(tmpddsfreqstr , Strlen) Tmpint = 4 - Strlen Mid(ddsfreqstr , Tmpint , Strlen) = Tmpstr End If End Sub End 'Note: when Fsteps,fblink changes, Fstepsnum constant must be updated accordingly! 'Frequency step values Fsteps: Data 0% , 1% , 2% , 3% , 4% , 5% , 6% , 7% Fblink: Data 11 , 10 , 9 , 7 , 6 , 5 , 3 , 2 , 1