;****************************************************************************** ; Source code used for Direct Digital Synthesis VFO ; ; Target Controller - PIC16F84 ; __________ ; ENCODER SWITCH--RA2 |1 18| RA1---------ENCODER A ; PB SWITCH-------RA3 |2 17| RA0---------ENCODER B ; +5V-------------RA4 |3 16| OSC1--------XTAL ; Ground--------!MCLR |4 15| OSC2--------XTAL ; Ground----------Vss |5 14| VDD---------+5 V ; DDS LOAD--------RB0 |6 13| RB7---------DDS DATA/LCD 14 ; LCD_rs----------RB1 |7 12| RB6---------LCD 13 ; LCD_rw----------RB2 |8 11| RB5---------DDS CLOCK/LCD 12 ; LCD_e-----------RB3 |9 10| RB4---------LCD 11 ; ---------- ; ; Assembler - Parallax SPASM v4.7 ; Author - Curtis W. Preuss - WB2V ; ; Modification History ; Initial Version 8/19/98 ; ;****************************************************************************** ;Description: ;This is the control program for a DDS VFO built with an AD9850 DDS chip, a ;shaft encoder, a push button switch and an Liquid crystal display. ; ;Features: ;VARIABLE RATE TUNING based on the speed at which the encoder is turned. The ;encoder also has a built in switch which will change the step size from 1Hz ;to 1kHz if the encoder shaft is pressed down while turning. ; ;BAND MEMORIES an external push button switch allows the frequency to be ;cycled around the HF ham bands. ; ;CALIBRATE MODE is entered if the external push button is pressed during ;power on. The display is set to 10,000.000 CAL. If the push button is held ;pressed then turning the shaft encoder will increase or decrease the value ;"osc" used to calculated the DDS control word. A fast adjustment speed is ;available by pressing the encoder shaft down while turning. ;To exit calibrate mode, release the external push button and turn the shaft ;encoder one more time. The calibrated value of "osc" will then be stored in ;EEprom memory. ; ;****************************************************************************** ;Device type and options DEVICE PIC16F84,XT_OSC,PROTECT_OFF,WDT_ON ;****************************************************************************** ;Constants used during program assembly pBCD equ 10h pAD9850 equ 15h SETREN equ 40h ;controls variable rate tuning feel ADDREN equ 08h ;controls variable rate tuning rate ;****************************************************************************** ;Assign names to IO pins DDS_load equ PortB.0 ;Update pin on AD9850 LCD_rs equ PortB.1 ;0=instruction, 1=data LCD_rw equ PortB.2 ;0=write, 1=read LCD_e equ PortB.3 ;0=disable, 1=enable DDS_clk equ PortB.5 ;AD9850 write clock DDS_dat equ PortB.7 ;AD9850 serial data input PbSwitch equ PortA.3 ;Calibrate Push Button, (active low) ;****************************************************************************** ;Allocate variables in general purpose register space org 0Ch freq ds 4 ;Display frequency in HEX bcd ds 5 ;Display frequency in BCD ad9850 ds 5 ;AD9850 control word fstep ds 4 ;frequency increment/decrement bcdCount ds 1 ;used in BIN2BCD routine bcdTemp ds 1 ; " multCount ds 1 ;used in Calc_DDS_Word routine bitCount ds 1 ;used in Send_DDS_Word routine byte2send ds 1 osc ds 4 osc_temp ds 4 ;oscillator frequency LCD_char ds 1 ;character being sent to LCD LCD_read ds 1 ;character read from LCD timer1 ds 1 ;used in delay subroutines timer2 ds 1 ; " ren_timer ds 2 ;used for variable rate tuning ren_new ds 1 ;new value of encoder pins A and B ren_old ds 1 ;old value of encoder pins A and B ren_read ds 1 ;encoder pins A, B and switch's Last_dir ds 1 ;indicates last direction of encoder Next_dir ds 1 ;indicates expected direction W_copy ds 1 ;used by interupt handler S_copy ds 1 ;used by interupt handler count ds 1 ;loop counter, (gets reused) band ds 1 ;used to index a table of frequencies ;*************************************************************************** EEORG 000h ;Default oscillator frequency EEDATA 70h,91h,0CAh,23h ;The fourth byte "23h" is the integer part of (2**32 / oscillator_freq_in_Hertz) ; ;Bytes 3,2,1 "CAh, 91h, 70h" are the fractional_part_of ; (2**32 / oscillator_freq_in_Hertz) times 2**24 ;****************************************************************************** ;The 16F84 resets to 000h, Interrupt vector is at 004h org 000h jmp Start ;Jump to beginning of program ;****************************************************************************** ; Interrupt handler saves copies of the W and STATUS registers on entry ; and restores the original register values on exit. org 004h ;Interrupt Vector mov w_copy,w ; Make a copy of w. mov s_copy,status ; Make a copy of status. ;interupt stuff goes here :return mov status,s_copy ; Restore status register swap w_copy ; Prepare for swapped move. mov w,<>w_copy ; Swap/move to w, status unaffected. reti ; Return to main program. ;The program starts here on a reset Start clr INTCON ;no interrupts for now setb RP0 ;Switch to register bank 1 setb RBPU ;Disable weak pull ups mov TRISA,#0FFh ;Tristate Port A clr TRISB ;Enable PortB clrb RP0 ;Switch back to register bank 0 ;****************************************************************************** ;Power on initialization of Liquid Crystal Display. ; The LCD controller chip must be equivalent to an Hitachi 44780. ; The LCD is assumed to be a 16 X 1 display. call Wait128 ;Wait for LCD to power up mov PortB,#30h ;LCD init_instruction setb LCD_e call Wait128 clrb LCD_e mov PortB,#30h ;LCD init_instruction setb LCD_e call Wait64 clrb LCD_e mov PortB,#30h ;LCD init_instruction setb LCD_e call Wait64 clrb LCD_e mov PortB,#20h ;4bit_mode_instruction setb LCD_e call Wait32 clrb LCD_e mov LCD_char,#28h ;2 line display call Cmnd2LCD mov LCD_char,#08h ;Display off call Cmnd2LCD mov LCD_char,#01h ;Clear and Reset Cursor call Cmnd2LCD mov LCD_char,#06h ;Set cursor move direction call Cmnd2LCD mov LCD_char,#0Ch ;Cursor blink off, Display On call Cmnd2LCD ;Enter Calibrate Mode if push button is pressed while turning the power on. jb PbSwitch,:readEEocs call Calibrate :readEEocs clr EEADR call ReadEE mov osc+0,EEDATA call ReadEE mov osc+1,EEDATA call ReadEE mov osc+2,EEDATA call ReadEE mov osc+3,EEDATA mov freq+0,#028h ;set the power on frequency mov freq+1,#001h ;14.025 MHz mov freq+2,#0D6h mov freq+3,#000h ;Display the power on frequency call BIN2BCD call ShowFreq ;Send Power on Frequency data to DDS chip call Calc_DDS_Word call Send_DDS_Word ;puts AD9850 in serial mode call Send_DDS_Word ;sends power on frequency to AD9850 ;Get the power on encoder value mov ren_read,PortA mov W,#03h and W,ren_read mov ren_old,W ;Set variables which require initialization clr ren_timer+1 mov ren_timer+0,#SETREN clr Last_dir clr band ;****************************************************************************** ;This is the Main Program Loop ;The program's main loop "MAIN" calls PollEncoder which continuously polls the ;rotary shaft encoder. When the shaft encoder has changed then the direction ;it moved is determined and stored in "Last_dir" the subroutine then returns to ;to MAIN. ; ;If the push button swith was not pressed then the variable "fstep" is ;calculated based on the delay between shaft encoder changes, ("ren_timer" ;contains the delay value determined by the PollEncoder subroutine). ;The variable "fstep" is added or subtracted from the current VFO frequency ;stored in "freq". The contents of "freq" are then converted to a BCD ;number in subroutine "BIN2BCD". The subroutine "ShowFreq" is then called to ;display the result on the Liquid Crystal Display. Next, the subroutine ;"Calc_DDS_Word" is used to calculate the DDS frequency control word from the ;values in "freq" and "osc". The result is stored in "AD9850". This data ;is transferred to the AD9850 DDS chip by calling the subroutine "Send_DDS_Word". ; ;If the push button is pressed while turning the encoder then "freq" is loaded ;with a constant stored in "BandTable". The variable "band" is used as an index ;into the table. "Band" is incremented or decremented based on the ;encoder direction. Main call PollEncoder jnb ren_read.3,:changeBand ;determine step size to use, (1 Hz or 1 kHz) clr fstep+3 clr fstep+2 clr fstep+1 mov fstep+0,#01h ;take 1 Hz steps if encoder switch is false jb ren_read.2,:goStep mov fstep+0,#0E8h ;take 1 kHz steps if encoder switch is true mov fstep+1,#003h jmp :goStep ;adjust the tuning step based on ren_timer :bumpstep clc rl fstep+0 rl fstep+1 rl fstep+2 rl fstep+3 :goStep rl ren_timer+0 rl ren_timer+1 jnc :bumpstep jb Last_dir.1,:up :down call sub_step jmp :write :up call add_step jmp :write :changeBand jb Last_dir.1,:bandUp :bandDwn sub band,#04h cjbe band,#20h,:valid mov band,#20h :valid call GetBand jmp :write :bandUp add band,#04h ;table entries are 4 byte apart csbe band,#20h clr band call GetBand :write call BIN2BCD call ShowFreq call Calc_DDS_Word call Send_DDS_Word jmp Main ;continue polling the encoder ;*************************************************************************** BandTable jmp PC+W retw 000h,01Bh,077h,040h ;160 meter band retw 000h,035h,067h,0E0h ; 80 meter band retw 000h,06Ah,0CFh,0C0h ; 40 meter band retw 000h,09Ah,01Dh,020h ; 30 meter band retw 000h,0D5h,09Fh,080h ; 20 meter band retw 001h,013h,0B2h,020h ; 17 meter band retw 001h,040h,06Fh,040h ; 15 meter band retw 001h,07Bh,0CAh,090h ; 12 meter band retw 001h,0ABh,03Fh,000h ; 10 meter band GetBand mov W,band call BandTable mov freq+3,W inc band mov W,band call BandTable mov freq+2,W inc band mov W,band call BandTable mov freq+1,W inc band mov W,band call BandTable mov freq+0,W sub band,#03h ;restore orignal value of "band" ret ;************************************************************************** add_step add freq+0,fstep+0 jnc :add1 ijnz freq+1,:add1 ijnz freq+2,:add1 inc freq+3 :add1 add freq+1,fstep+1 jnc :add2 ijnz freq+2,:add2 inc freq+3 :add2 add freq+2,fstep+2 jnc :add3 inc freq+3 :add3 add freq+3,fstep+3 ;Peg the maximum VFO output frequency to ;a predetermined value, ( 1C9C380 is 30 MHz) cja freq+3,#001h,:setMax cjb freq+3,#001h,:exit cja freq+2,#0C9h,:setMax cjb freq+2,#0C9h,:exit cja freq+1,#0C3h,:setMax cjb freq+1,#0C3h,:exit cjb freq+0,#080h,:exit :setMax mov freq+0,#080h mov freq+1,#0C3h mov freq+2,#0C9h mov freq+3,#001h :exit ret ;****************************************************************************** sub_step not fstep+0 ;subtraction of fstep from not fstep+1 ;freq is down by adding the not fstep+2 ;twos compliment of fstep to not fstep+3 ;freq ijnz fstep+0,:compDone ijnz fstep+1,:compDone ijnz fstep+2,:compDone inc fstep+3 :compDone call add_step ;Peg the minimum VFO output frequency to ;a predetermined value, ( 3E8 is 1 kHz) jb freq+3.7,:setMin ;test for a negative number test freq+3 jnz :exit test freq+2 jnz :exit cjb freq+1,#003h,:setMin cja freq+1,#003h,:exit cja freq+0,#0E8h,:exit :setMin mov freq+0,#0E8h mov freq+1,#003h clr freq+2 clr freq+3 :exit ret ;****************************************************************************** PollEncoder clr ren_timer+1 mov ren_timer+0,#SETREN ;put starting value in ren_timer :read_encoder clr WDT ;reset the watch dog timer jb ren_timer+1.7,:noInc add ren_timer+0,#ADDREN snc inc ren_timer+1 :noInc mov REN_read,PORTA mov W,#03h and W,ren_read ;isolate direction bits mov ren_new,W ;save the direction bits xor W,ren_old jz :read_encoder ;Loop until encoder is unchanged ;determine which direction the encoder turned. clc rl ren_old xor ren_old,ren_new ;prevent encoder slip from giving a false change in direction mov W,ren_old and W,#00000010b mov Next_dir,W xor W,Last_dir jz :continue ;no slip keep going mov Last_dir,Next_dir mov ren_old,ren_new jmp :read_encoder :continue jb ren_old.1,:up clr Last_dir jmp :exit :up mov Last_dir,#00000010b :exit mov ren_old,ren_new ret ;****************************************************************************** Calibrate mov freq+0,#080h ;set frequency to 10MHz mov freq+1,#096h mov freq+2,#098h mov freq+3,#000h mov osc+0,#006h ;load the default oscillator frequency mov osc+1,#0F0h mov osc+2,#0CAh mov osc+3,#023h call BIN2BCD call ShowFreq mov LCD_char,#0C4h ;point LCD at digit # "14" call Cmnd2LCD mov LCD_char,#'C' ;send a C call Data2LCD mov LCD_char,#'A' ;send a A call Data2LCD mov LCD_char,#'L' ;send a L call Data2LCD :Cal_Loop call Calc_DDS_Word call Send_DDS_Word call PollEncoder clr fstep+3 clr fstep+2 clr fstep+1 mov fstep+0,#10h ;slow adjust jb ren_read.2,:updateOSC mov fstep+0,#80h ;fast adjust if encoder switch down :updateOSC nop jb Last_dir.1,:faster :slower not fstep+0 ;subtraction of fstep not fstep+1 ;is done by adding the not fstep+2 ;twos compliment of fstep to not fstep+3 ;osc ijnz fstep+0,:faster ijnz fstep+1,:faster ijnz fstep+2,:faster inc fstep+3 :faster add osc+0,fstep+0 jnc :add1 ijnz osc+1,:add1 ijnz osc+2,:add1 inc osc+3 :add1 add osc+1,fstep+1 jnc :add2 ijnz osc+2,:add2 inc osc+3 :add2 add osc+2,fstep+2 jnc :add3 inc osc+3 :add3 add osc+3,fstep+3 :continue jnb ren_read.3,:Cal_Loop clr EEADR ;write final value to EEPROM mov EEDATA,osc+0 call WriteEE mov EEDATA,osc+1 call WriteEE mov EEDATA,osc+2 call WriteEE mov EEDATA,osc+3 call WriteEE ret ;****************************************************************************** ;Multiply the 32 bit number for oscillator frequency times the 32 bit number ;for the displayed frequency. Calc_DDS_Word clr AD9850+0 ;clear AD9850 registers clr AD9850+1 clr AD9850+2 clr AD9850+3 clr ad9850+4 mov multCount,#32 mov osc_temp+0,osc+0 mov osc_temp+1,osc+1 mov osc_temp+2,osc+2 mov osc_temp+3,osc+3 :mult_loop clc jnb osc_temp+0.0,:noAdd add ad9850+1,freq+0 ;note the 8 bit shift jnc :add1 ijnz ad9850+2,:add1 ijnz ad9850+3,:add1 inc ad9850+4 :add1 add ad9850+2,freq+1 jnc :add2 ijnz ad9850+3,:add2 inc ad9850+4 :add2 add ad9850+3,freq+2 jnc :add3 inc ad9850+4 :add3 add ad9850+4,freq+3 :noAdd rr ad9850+4 rr ad9850+3 rr ad9850+2 rr ad9850+1 rr ad9850+0 rr osc_temp+3 rr osc_temp+2 rr osc_temp+1 rr osc_temp+0 djnz multCount,:mult_loop clr ad9850+4 ;clear the ad9850 phase and control byte ret ;****************************************************************************** ;Send Control Word to AD9850 DDS chip ;This routine does a serial data transfer to the AD9850 Send_DDS_Word mov FSR,#pAD9850 ;point at AD9850+0 :next_byte mov byte2send,indirect mov bitCount,#8 ;set a counter to 8 :next_bit rr byte2send ;test if bit to send is 1 or 0 jnc :send0 setb DDS_dat ;send a 1 setb DDS_clk ;toggle write clock clrb DDS_clk jmp :break :send0 clrb DDS_dat ;send a 0 setb DDS_clk ;toggle write clock clrb DDS_clk :break decsz bitCount ;test if entire byte has been sent jmp :next_bit inc FSR ;start the next byte unless finished cjb FSR,#pAD9850+5,:next_byte setb DDS_load ;send load signal to AD9850 clrb DDS_load ret ;****************************************************************************** ;This subroutine converts a 32 bit binary number to a 10 digit BCD number. ;The input value taken from freq(0 to 3) is preserved. ;The output is in bcd(0 to 4), each byte holds => (hi_digit,lo_digit), ;most significant digits are in bcd+4. This routine is a modified version of one ;described in MicroChip application note AN526. BIN2BCD mov bcdCount,#32 clr bcd+0 clr bcd+1 clr bcd+2 clr bcd+3 clr bcd+4 :loop clc rl freq+0 rl freq+1 rl freq+2 rl freq+3 snc setb freq+0.0 rl bcd+0 rl bcd+1 rl bcd+2 rl bcd+3 rl bcd+4 dec bcdCount jnz :adjust ret :adjust mov FSR,#pBCD call adjBCD inc FSR call adjBCD inc FSR call adjBCD inc FSR call adjBCD inc FSR call adjBCD jmp :loop adjBCD mov bcdTemp,indirect add bcdTemp,#33h clr W sb bcdTemp.3 or W,#03h sb bcdTemp.7 or W,#30h sub bcdTemp,W mov indirect,bcdTemp ret ;****************************************************************************** ;Display frequency setting on LCD ShowFreq mov LCD_char,#81h ;point LCD at digit # "1" call Cmnd2LCD mov LCD_char,bcd+3 ;send 10 MHz digit and LCD_char,#0F0h swap LCD_char or LCD_char,#30h call Data2LCD mov LCD_char,bcd+3 ;send 1 MHz digit and LCD_char,#0Fh or LCD_char,#30h call Data2LCD mov LCD_char,#',' ;send a comma call Data2LCD mov LCD_char,bcd+2 ;send 100 kHz digit and LCD_char,#0F0h swap LCD_char or LCD_char,#30h call Data2LCD mov LCD_char,bcd+2 ;send 10 kHz digit and LCD_char,#0Fh or LCD_char,#30h call Data2LCD mov LCD_char,bcd+1 ;send 1 kHz digit and LCD_char,#0F0h swap LCD_char or LCD_char,#30h call Data2LCD mov LCD_char,#'.' ;send a period call Data2LCD mov LCD_char,#0C0h ;point LCD at digit # "9" call Cmnd2LCD mov LCD_char,bcd+1 ;send 100 Hz digit and LCD_char,#0Fh or LCD_char,#30h call Data2LCD mov LCD_char,bcd+0 ;send 10 Hz digit and LCD_char,#0F0h swap LCD_char or LCD_char,#30h call Data2LCD mov LCD_char,bcd+0 ;send 1 Hz digit and LCD_char,#0Fh or LCD_char,#30h call Data2LCD mov LCD_char,#' ' ;send a space call Data2LCD mov LCD_char,#'k' ;send a k call Data2LCD mov LCD_char,#'H' ;send a H call Data2LCD mov LCD_char,#'z' ;send a z call Data2LCD ret ;****************************************************************************** ;Check if LCD is done with last operation Busy_Check clr PORTB setb RP0 mov TRISB,#0F0h clrb RP0 ;tristate the data pins clrb LCD_rs ;setup LCD for commands setb LCD_rw ;setup LCD for read mov timer1,#255 :lcd_is_busy setb LCD_e mov LCD_read,PortB clrb LCD_e nop nop setb LCD_e nop clrb LCD_e dec timer1 ;try it 255 times then exit anyway jz :not_busy jb LCD_read.7,:lcd_is_busy :not_busy ret ;****************************************************************************** ;Send Command to LCD Subroutine ;The command to be sent must in LCD_char before calling the subroutine. Cmnd2LCD call Busy_Check clr PortB setb RP0 ;enable the data pins mov TRISB,#00h clrb RP0 clrb LCD_rs ;setup LCD for commands clrb LCD_rw ;setup LCD for write and PortB,#0Fh ;transfer 1st nibble mov W,LCD_char and W,#11110000b or PortB,W setb LCD_e nop clrb LCD_e and PortB,#0Fh ;transfer 2nd nibble swap lcd_char mov W,LCD_char and W,#11110000b or PortB,W setb LCD_e nop clrb LCD_e :exit ret ;****************************************************************************** ;Send Data to LCD Subroutine ;The data to be sent must in LCD_char before calling the subroutine. Data2LCD call Busy_Check clr PortB setb RP0 ;enable the data pins mov TRISB,#00h clrb RP0 setb LCD_rs ;setup LCD for data clrb LCD_rw and PortB,#0Fh ;transfer 1st nibble mov W,LCD_char and W,#11110000b or PortB,W setb LCD_e nop clrb LCD_e and PortB,#0Fh ;transfer 2nd nibble swap lcd_char mov W,LCD_char and W,#11110000b or PortB,W setb LCD_e nop clrb LCD_e :exit ret ;****************************************************************************** WriteEE setb RP0 setb WREN mov EECON2,#55h mov EECON2,#0AAh setb WR jb WR,$ clrb WREN clrb RP0 inc EEADR ret ;************************************************************************** ReadEE setb RP0 setb RD clrb RP0 inc EEADR ret ;****************************************************************************** ;Delay subroutines Wait255 mov timer1,#255 jmp Wait16:outer Wait128 mov timer1,#128 jmp Wait16:outer Wait64 mov timer1,#64 jmp Wait16:outer Wait32 mov timer1,#32 jmp Wait16:outer Wait16 mov timer1,#16 :outer mov timer2,#255 :inner djnz timer2,:inner djnz timer1,:outer ret ;****************************************************************************** ;The END