;************************************************************************** ; Source code for Direct Digital Synthesis VFO controller ; ; Based on original program written for an AD9850 by ; Curtis W. Preuss - WB2V ; 11/08/96 ; Initial creation ; 10/14/98 ; Ported source to Microchip MPASM -- Craig B. Johnson, AA0ZZ ; Target Controller - PIC16f84a ; ; Further changes ; ; 10/02/05 ; Modified for an AD9851 with a 30MHz crystal oscillator and ; x6 multiplier enabled. AD9851_4 contains the control bits ; and the digit weights have been changed accordingly. ; ;************************************************************************** list p=16f84 radix hex Indirect equ 0 Status equ 3 FSR equ 4 PortA equ 5 PortB equ 6 Enable equ 1 ; Enable x6 multiplier. If this is set to 0 ; the AD9851 will work as an AD9850 ;Assign names to IO pins LCD_VDD equ 3 ;(PortA)Power to LCD module AD9851_fqu equ 0 ;(PortB)Update pin on AD9851 LCD_rs equ 1 ;(PortB)0=instruction, 1=data LCD_rw equ 2 ;(PortB)0=write, 1=read LCD_e equ 3 ;(PortB)0=disable, 1=enable AD9851_wc equ 5 ;(PortB)AD9851 write clock AD9851_dat equ 7 ;(PortB)AD9851 serial data input ;Allocate variables in general purpose register space LCD_Dat equ 0x0C ;current Display data AD9851 equ 0x13 ;5 byte control word for DDS chip OP1 equ 0x18 ;Work Space for OP2 equ 0x1C ; the multiply routine temp1 equ 0x1D ;Temp storage temp2 equ 0x1E ;Temp storage temp3 equ 0x1F ;Temp storage old equ 0x20 ;old encoder input data new equ 0x21 ;new encoder input data count1 equ 0x22 ;used for timing the shaft encoder count2 equ 0x23 ; ;Status Bits C equ 0 ;Carry DC equ 1 ;Digit (Half) Carry Z equ 2 ;Zero org 0 goto Start org 5 ;************************************************************************** ;BusyCheck Subroutine ; ;LCD read/write operations are slooooow, this subroutine ;polls the LCD busy flag to determine if previous operations are completed. ;Note side effect that the LCD_rw and LCD_rs are left in write data mode. BusyCheck movlw 0xF0 ;Tristate the 4 data pins tris PortB ; bsf PortB,LCD_rw ;Raise the r/w bit to read the LCD Busy bsf PortB,LCD_e ; movf PortB,w ;Get first nibble of input data movwf temp1 ; and save it bcf PortB,LCD_e ; nop ; bsf PortB,LCD_e ;Get next nibble to keep LCD happy bcf PortB,LCD_e ; btfsc temp1,7 ;Check for busy flag goto Busy ;It is busy bcf PortB,LCD_rw ;Leave LCD in Write Data mode bsf PortB,LCD_rs ; return ;************************************************************************** ;Send Character to LCD Subroutine ; ;The character to be sent must have been placed in "temp2" prior to calling ;the subroutine. LCD_rw and LCD_rs must be set up prior to entry. SendChar movlw 0x00 ; tris PortB ;Enable port B movf temp2,w ;Save temp2 movwf temp1 ; in temp1 movlw 0x0F ; andwf PortB,f ; movlw 0xF0 ; andwf temp2,f ; movf temp2,w ; iorwf PortB,f ;Load first nibble bsf PortB,LCD_e ;Transfer first nibble bcf PortB,LCD_e ; swapf temp1,f ; movlw 0x0F ; andwf PortB,f ; movlw 0xF0 ; andwf temp1,f ;Clear data nibble movf temp1,w ; iorwf PortB,f ; bsf PortB,LCD_e ;Transfer second nibble bcf PortB,LCD_e ; return ;************************************************************************** ;BCD increment ; ;This routine does a binary coded decimal increment to the values in LCD_Dat. ;On entry the FSR register must point to the LCD_Dat digit to be incremented. ;If the result is over 60MHz the value is reset back to 60MHz. BCDInc movf Indirect,w ; movwf temp2 ; movlw 0x01 ; addwf temp2,f ; movf temp2,w ; movwf temp1 ; movlw 0x06 ; addwf temp1,f ; btfsc Status, DC ;See if Digit Carry is clear goto BCDIncCarry ;Digit Carry is set, so jump movlw 0x0F ; andwf temp2,f ; movf temp2,w ; movwf Indirect ; movlw 0x06 ;Max freq 60MHz subwf LCD_Dat+0,w ;Test for max frequency btfsc Status, C ;See if Carry is clear (LCD_Dat+0<6) goto SetMax ;Carry is set, so jump return BCDIncCarry clrf temp2 ; movf temp2,w ; movwf Indirect ; decf FSR,f ; goto BCDInc ; SetMax ;Set the display to 60,000.00 movlw 0x12 ;Low digit movwf FSR ;Point to it SetMax1 movlw 0x00 ; movwf Indirect ;Zero and repeat for five more digits decf FSR,f ; movlw 0xF3 ; addwf FSR,w ; btfsc Status, C ;See if Carry is clear goto SetMax1 ;Carry is set, so jump movlw 0x06 ;Set digit to high x10MHz count movwf Indirect ;Put in LCD_Dat+0 return ; ;************************************************************************** ;BCD decrement ; ;This routine does a binary coded decimal decrement to the values in LCD_Dat. ;On entry the FSR register must point to the LCD_Dat digit to be decremented, ;(one of 10's, 100's, 1K, 10K or 100K digit) ;If the result is under 100KHz the value is reset back to 100KHz. BCDDec movf Indirect,w ; movwf temp2 ; movlw 0x01 ; subwf temp2,f ; btfss Status, DC ;See if Digit Carry is set goto BCDDecCarry ;Digit Carry is clear, so jump movf temp2,w ; movwf Indirect ; movlw 0xFF ; addwf LCD_Dat+0,w ; btfsc Status, C ;See if Carry is clear goto BCDDecExit ;Carry is set, so jump to exit movlw 0xFF ; addwf LCD_Dat+1,w ; btfsc Status, C ;See if Carry is clear goto BCDDecExit ;Carry is set, so jump to exit movlw 0xFF ; addwf LCD_Dat+2,w ; btfsc Status, C ;See if Carry is clear goto BCDDecExit ;Carry is set, so jump to exit goto SetMin ; BCDDecExit return BCDDecCarry movlw 0x09 ; movwf temp2 ; movf temp2,w ; movwf Indirect ; decf FSR,f ; goto BCDDec ; SetMin movlw 0x12 ; movwf FSR ; SetMin1 movlw 0x00 ; movwf Indirect ; decf FSR,f ; movlw 0xF1 ; addwf FSR,w ; btfsc Status, C ;See if Carry is clear goto SetMin1 ;Carry is set, so jump movlw 0x01 ; movwf Indirect ; decf FSR,f ; movlw 0x00 ; movwf Indirect ; decf FSR,f ; movlw 0x00 ; movwf Indirect ; return ; ;************************************************************************** ; Delay 24ms subroutine ; Wait24ms movlw 0x28 ; use 40 for 24ms wait if clock is 4Mhz movwf temp1 ; Wait24Outer movlw 0xC8 ; use 200 for 24 ms wait movwf temp2 ; Wait24Inner decfsz temp2,f ; goto Wait24Inner ; decfsz temp1,f ; goto Wait24Outer ; return ;************************************************************************** ; Delay 60ms subroutine ; Wait60ms movlw 0x64 ; use 100 for 60ms wait if clock is 4Mhz movwf temp1 ; Wait60Outer movlw 0xC8 ; use 200 for 60ms wait movwf temp2 ; Wait60Inner decfsz temp2,f ; goto Wait60Inner ; decfsz temp1,f ; goto Wait60Outer ; return ; ;************************************************************************** ; 32 bit add ; ;The value in registers OP1 is added to the current 32 bit value in AD9851 Add32 movf OP1+0,w ; addwf AD9851+0,f ; btfss Status, C ;See if Carry is set goto Add1 ;Carry is clear, so jump incfsz AD9851+1,f ; goto Add1 ; incfsz AD9851+2,f ; goto Add1 ; incf AD9851+3,f ; Add1 movf OP1+1,w ; addwf AD9851+1,f ; btfss Status, C ;See if Carry is set goto Add2 ;Carry is clear, so jump incfsz AD9851+2,f ; goto Add2 ; incf AD9851+3,f ; Add2 movf OP1+2,w ; addwf AD9851+2,f ; btfss Status, C ;See if Carry is set goto Add3 ;Carry is clear, so jump incf AD9851+3,f ; Add3 movf OP1+3,w ; addwf AD9851+3,f ; return ; ;************************************************************************** ;Write Data ; ;This subroutine does two things: ; (1) it sends the contents LCD_dat to the LCD, ; (LCD_Dat must contain BCD digits) ; (2) the contents of AD9851 (40 bits) are serial shifted ; into the AD9851 module WriteData movlw 0x83 ; LCD position for first digit movwf temp2 ; call BusyCheck ; bcf PortB,LCD_rs ; Set LCD for instruction write call SendChar ; movf LCD_Dat+0,w ; movwf temp2 ; movlw 0x20 ;Send a space if first digit is zero iorwf temp2,f ; movlw 0x20 ;Set up for space check subwf temp2,w ;Subtract to compare btfsc Status, Z ;See if Zero is clear (not a space) goto Wd2 ;Zero is set, so go send the space movlw 0x10 ;Zero is clear, so convert BCD iorwf temp2,f ; to ASCII Wd2 call BusyCheck ; call SendChar ;Send 10Mhz digit movf LCD_Dat+1,w ; movwf temp2 ; movlw 0x30 ; iorwf temp2,f ; call BusyCheck ; call SendChar ;Send 1Mhz digit movlw 0x2C ; movwf temp2 ; call BusyCheck ; call SendChar ;Send a comma movf LCD_Dat+2,w ; movwf temp2 ; movlw 0x30 ; iorwf temp2,f call BusyCheck ; call SendChar ;Send a 100KHz digit movf LCD_Dat+3,w movwf temp2 movlw 0x30 iorwf temp2,f call BusyCheck ;Send 10 KHz digit call SendChar movlw 0xC0 ;Point LCD at digit#9 (Gap in LCD address) movwf temp2 call BusyCheck ; bcf PortB,LCD_rs ;Set LCD for instruction write call SendChar ; movf LCD_Dat+4,w ; movwf temp2 movlw 0x30 iorwf temp2,f call BusyCheck call SendChar ; Send 1Khz digit movlw 0x2E movwf temp2 call BusyCheck call SendChar ; Send a period movf LCD_Dat+5,w ; movwf temp2 movlw 0x30 iorwf temp2,f call BusyCheck call SendChar ; Send 100Hz digit movf LCD_Dat+6,w ; movwf temp2 movlw 0x30 iorwf temp2,f call BusyCheck ; call SendChar ; Send 10Hz digit WriteAD9851 clrf PortB ; Clear PortB movlw 0x05 ; Send 5 bytes movwf temp3 ; to AD9851 movlw 0x00 ; tris PortB ; Enable PortB movlw 0x13 ; Point at AD9851+0 movwf FSR NextByte movf Indirect,w ; movwf temp1 movlw 0x08 ; set bit counter to 8 movwf temp2 NextBit rrf temp1,f btfss Status, C ;See if carry is set goto Send0 ;Carry is clear, so jump bsf PortB,AD9851_dat bsf PortB,AD9851_wc ; toggle write clock bcf PortB,AD9851_wc ; (repeated 40 times) goto Break Send0 bcf PortB,AD9851_dat bsf PortB,AD9851_wc bcf PortB,AD9851_wc Break decfsz temp2,f goto NextBit incf FSR,f decfsz temp3,f goto NextByte bsf PortB,AD9851_fqu ; send load signal to AD9851 bcf PortB,AD9851_fqu ; Clear after last data dbit is written return ;*************************************************************************** ;7 Digit BCD by 32 bit binary mulitply ; ;The 7 BCD digits stored in LCD_dat are each multiplied by a precalculated digit ;weight. Digit weigths assume a 30MHz input clock with the x6 multiplier enabled ;The sum of all 7 digits times their weigth is stored in the AD9851 registers. ; Mult clrf temp1 clrf AD9851+0 ; Clear all five bytes of clrf AD9851+1 ; AD9851 control word clrf AD9851+2 ; W0 thru W4 clrf AD9851+3 clrf AD9851+4 ; First 3 bits contain control movlw Enable ; Enable the x6 multiplier movwf AD9851+4 Mult10 movlw 0xEF ;EF movwf OP1+0 clrf OP1+1 clrf OP1+2 clrf OP1+3 movf LCD_Dat+6,w ; movwf OP2 ; goto Go Mult100 movlw 0x52 ;0952 movwf OP1+0 ; movlw 0x09 ; movwf OP1+1 clrf OP1+2 clrf OP1+3 movf LCD_Dat+5,w ; movwf OP2 goto Go Mult1K movlw 0x35 ;5D35 movwf OP1+0 ; movlw 0x5D ; movwf OP1+1 clrf OP1+2 ; clrf OP1+3 movf LCD_Dat+4,w ; movwf OP2 goto Go Mult10K movlw 0x11 ;03A411 movwf OP1+0 movlw 0xA4 ; movwf OP1+1 movlw 0x03 ; movwf OP1+2 clrf OP1+3 ; movf LCD_Dat+3,w ; movwf OP2 goto Go Mult100K movlw 0xAA ;2468AA movwf OP1+0 movlw 0x68 ; movwf OP1+1 movlw 0x24 ; movwf OP1+2 clrf OP1+3 movf LCD_Dat+2,w ; movwf OP2 ; goto Go Mult1M movlw 0xA3 ;016C16A3 movwf OP1+0 movlw 0x16 ; movwf OP1+1 movlw 0x6C ; movwf OP1+2 movlw 0x01 ; movwf OP1+3 movf LCD_Dat+1,w ; movwf OP2 ; goto Go Mult10M movlw 0x5D ;0E38E25D movwf OP1+0 movlw 0xE2 ; movwf OP1+1 movlw 0x38 ; movwf OP1+2 movlw 0x0E ; movwf OP1+3 movf LCD_Dat+0,w ; movwf OP2 ; goto Go Go movlw 0x04 movwf temp2 DigitLoop bcf Status, C ;Clear Carry bit rrf OP2,f ;Rotate right (through carry) btfss Status, C ;See if Carry is set goto NoCarry ;Carry is clear, so jump call Add32 NoCarry bcf Status, C ;Clear Carry bit rlf OP1+0,f ;Rotate left through carry rlf OP1+1,f ; rlf OP1+2,f ; rlf OP1+3,f ; decfsz temp2,f goto DigitLoop incf temp1,f movlw 0x01 subwf temp1,w btfsc Status, Z ;See if Zero is clear goto Mult100 ;Zero is set, so jump movlw 0x02 subwf temp1,w btfsc Status, Z ;See if Zero is clear goto Mult1K ;Zero is set, so jump movlw 0x03 subwf temp1,w btfsc Status, Z ;See if Zero is clear goto Mult10K ;Zero is set, so jump movlw 0x04 subwf temp1,w btfsc Status, Z ;See if Zero is clear goto Mult100K ;Zero is set, so jump movlw 0x05 subwf temp1,w btfsc Status, Z ;See if Zero is clear goto Mult1M ;Zero is set, so jump movlw 0x06 subwf temp1,w btfsc Status, Z ;See if Zero is clear goto Mult10M ;Zero is set, so jump return ;************************************************************************** ;Initialize PIC controller ; ; Start here after reset or power on ; Set DDS initially to 30,000.00 Start clrf PortA ; Clear data Port A clrf PortB ; Clear data Port B movlw 0x00 ; tris PortB ; Enable port B drivers movlw 0xFF ; tris PortA ; Tristate Port A drivers movlw 0x03 ; Set initial LCD data for 35MHz movwf LCD_Dat+0 movlw 0x05 movwf LCD_Dat+1 movlw 0x00 movwf LCD_Dat+2 movlw 0x00 movwf LCD_Dat+3 movlw 0x00 movwf LCD_Dat+4 movlw 0x00 movwf LCD_Dat+5 movlw 0x00 movwf LCD_Dat+6 clrf count1 clrf count2 ;************************************************************************** ;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. movlw 0x07 ; Enable LCD_VDD pin tris PortA ; power up LCD bsf PortA,LCD_VDD call Wait60ms ; Wait for LCD to power up (>15ms) bsf PortB,LCD_e ; movlw 0x38 ; func set + enable to LCD movwf PortB bcf PortB,LCD_e call Wait60ms ; Can't check BF yet, so wait >4.1ms bsf PortB,3 movlw 0x38 ; func set + enable to LCD movwf PortB bcf PortB,LCD_e ; call Wait60ms ; Can't check BF yet, so wait >4.1ms bsf PortB,LCD_e movlw 0x38 ; func set + enable to LCD movwf PortB bcf PortB,LCD_e ; call Wait60ms ; Can't check BF yet, so wait >4.1ms bsf PortB,LCD_e movlw 0x28 ; 4bit_mode instruction + enable movwf PortB bcf PortB,LCD_e call Wait60ms ; Can check BF after here movlw 0x01 ; Clear and reset cursor movwf temp2 call BusyCheck bcf PortB,LCD_rs call SendChar movlw 0x06 ;increment no_shift mode movwf temp2 call BusyCheck bcf PortB,LCD_rs call SendChar movlw 0x0C ; Display on movwf temp2 call BusyCheck bcf PortB,LCD_rs call SendChar ; Disp should be on, clear and ready for data ;Send initial display value to LCD call Mult call Wait24ms ; allow AD9851 clock time to start call Wait24ms ; call WriteData ; side effect reset power down bits of AD9851 call WriteData ; load the initial display value ;Get the power on encoder value movf PortA,w ; get encoder value movwf temp2 movlw 0x03 andwf temp2,w ; isolate direction bits movwf old ; save initial values ;************************************************************************** ;Check encoder ; PortA.0 shaft encoder output A ; PortA.1 shaft encoder output B ; PortA.2 shaft encoder push button switch ; Timing checks assume a 4MHz cpu clock and that the encoder ; has 32 detents ChkEncoder incf count1,f movlw 0x75 ;Check encoder loop takes 17 us subwf count1,w btfss Status, C ;See if Carry is set goto Ce ;Carry is clear, so jump incf count2,f clrf count1 ;0x75 * 17us = 2ms Ce movf PortA,w ;Get encoder value movwf temp1 movf temp1,w movwf temp2 ;Double latch the input movlw 0x03 andwf temp2,w movwf new xorwf old,w btfsc Status, Z ;See if Zero is clear goto ChkEncoder ;Zero is set, so jump movlw 0x0E ;Pointer set to 100KHz digit movwf FSR btfss temp2,2 ;If pb switch is closed (active low) goto CeGo ; movlw 0x12 ;Pointer set to 10's movwf FSR movlw 0x0F subwf count2,w ;If speed < 1.0 RPM btfsc Status, C ;See if Carry is clear goto CeGo ;Carry is set, so jump movlw 0x11 ;Pointer set to 100's movwf FSR movlw 0x05 subwf count2,w ;If (1.0RPM < Speed < 3.0RPM) btfsc Status, C ;See if Carry is clear goto CeGo ;Carry is set , so jump movlw 0x10 ;Pointer set to 1K movwf FSR CeGo clrf count2 ;Add time to write LCD etc to count1 movlw 0x46 ; (about 1.2 ms) movwf count1 bcf Status, C ;Clear Carry bit rlf old,f ;Rotate left movf new,w xorwf old,f ;Determine direction btfsc old, DC ;See if Digit Carry is clear goto CeUp ;Digit Carry is set, so jump call BCDDec ;Calculate new LCD display call Mult ;Calculate new AD9851 control word goto CeWrite CeUp call BCDInc ; calculate new LCD display call Mult ; calculate new AD9851 control word CeWrite call WriteData movf new,w movwf old goto ChkEncoder ; continue polling the encoder ; ID ; ID ; ID ; ID ; Fuses (CP=Off, PWRTE=Enabled, WDTE=Disabled, OSC=XT) ; 0001 END