; *************************************************************************************************
; * W2EB - AD9854-based VFO for Phased Rigs                                                       *
; *  Version 2, 20140803                                                                          *
; *  9 January 2013                                                                               *
; *  Author: Bill Lazure,                                                                         *
; *	**Based on PICELGen 6.0 by AA0ZZ, Craig Johnson and PEGEN 2.1 by W3CD                        *
; *************************************************************************************************
; Description:                                                                                    *
; This is the control program for a DDS VFO built with an AD9854 DDS chip, a                      *
; shaft encoder, three push button switches, a Hitachi 44780-compatible                           *
; Liquid crystal display, and a 18F2520 PIC microcontroller.                                      *
;**************************************************************************************************
; * 9 Jan 13 - Changed all PORTS and Pin assignments to newer PCB layout.  Added DDS              *  
; *            initialization subroutine and new "Send_dds_word" routine. Added the 1MHz decade   *
; *            into the mix.                                                                      *
; *          -Reworked the LCD_Busy routine.  Due to dual function of PORTB, had to manually      *
; *           manipulate individual pins. May have gotten it wrong!!Suspect this if the LCD acts  *
; *           weird.                                                                              *
; *  23 Jan 13 - This is a test program.  I deleted unused EQUs, lengthened all waits associated  *
; *            with the DDS, and looped it back immediately following the DDS initialization.  I  * 
; *            don't know if the initialization word inputs freq, but I doubt it.  Use this       *
; *            program to simply see that signal leaves the port.  Once that's established, put   *  
; *            the loop after the DDS pre-load commands, lines 608 or 609.                        *
; * 2 Mar 15 - Commented out calls for IF Offset. Implemented PB3, Rx/Tx display.                 *
; * 3 Mar 15 - Enabled and revised Config line.  Added more delays in LCD initialization          *
; *            subroutine.                                                                        *
; *4 Mar 15 - Cleaned formatting, and removed commented-out Transmitter_Active_Look               *
; *8 Mar 15 - Added routine to allow cursor to always return to numeric digits rather than stay on*
; *           the last function used.  Added PB3 function. Put CONFIG info in program.            *
; *9 Mar 15 - Cleaned code and added bcf to LCD control lines preceding initialization to         *
; *           (hopefully) make initialization more reliable. Added CLRF PORTB on line 545.  Added * 
; *           setup for OSCCON to make oscillator run at 1MHz                                     *
; *10 Mar 15 - Changed "Poll Encoder" to match that of the original "decade-Change" Based         *
; *           program < http://www.vk5tm.com/homebrew/dds/PEGen2-1f.asm > to aid debouncing of    *
; *           mechanical encoder.                                                                 *
; *15 Mar 15 - Today I calibrated the VFO to within 20Hz of actual.  I swapped USB/LSB display to *
; *           match my transceiver, I reduced the LCD initialization time to something more       *
; *           reasonable.  I attempted to get PB4 operating, but failed in that attempt.          *
; *************************************************************************************************
                                                                             
; Target Controller -      PIC18F2520                             
;                        -------___-------                                         
; PIC_MCLR--------RE3 |1                 28| RB7---------DDS_RST (Pin 17)                  
; Band0-----------RA0 |2                 27| RB6---------DDS_SDI  (Pin 19)                   
; Band1-----------RA1 |3                 26| RB5---------DDS_IOCLK (Pin 20)                       
; Band2-----------RA2 |4                 25| RB4---------DDS_WR (Pin 21)                      
; Band3-----------RA3 |5                 24| RB3---------LCD_D7                       
; Rly1------------RA4 |6                 23| RB2---------LCD_D6   (LCD Pin 6)                     
; Rly2------------RA5 |7                 22| RB1---------LCD_D5  (LCD Pin 5)       
; GND-------------VSS |8                 21| RB0---------LCD_D4  (LCD Pin 4)           
; ENC1-----------RA7  |9                 20| VCC---------+5 V          
; ENC2-----------RA6  |10                19| VSS---------Gnd                      
; PB1-------------RC0 |11                18| RC7---------DDS_MCLR                 
; PB2-------------RC1 |12                17| RC6---------LCD_E         
; PB3-------------RC2 |13                16| RC5---------LCD_RW           
; PB4-------------RC3 |14                15| RC4---------LCD_RS         
;                       -------------------                                          
; **************************************************************************************************
; * Device type and options.                                                                       *
; **************************************************************************************************
;
          processor 18F2520
          #include	<P18F2520.inc>
          radix     dec
          errorlevel 0, -205, -220, -302; Skip nuisance messages 


; **************************************************************************************************
; * Configuration information for 18F2520:                                                         *
; **************************************************************************************************

	CONFIG OSC=INTIO67
	CONFIG BOREN=off
	CONFIG WDT=OFF
	CONFIG MCLRE=ON
	CONFIG PBADEN=OFF
	CONFIG DEBUG=OFF
	CONFIG LVP=OFF
	

;***************************************************************************************************
;                                                                                                  *
;		                     Hardware Configuration                                              *
;                                                                                                  *
;***************************************************************************************************
; *                                                                                                *
; * DDS Frequency control equates.  These may be changed to accommodate the                        *
; * reference clock frequency.                                                                     *
; **************************************************************************************************
;
; ref_osc represents the change in the frequency control word which results 
; in a 1 Hz change in output frequency.  It is interpreted as a fixed point
; integer in the format <ref_osc_3>.<ref_osc_2><ref_osc_1><ref_osc_0>
;
; The values for common oscillator frequencies are as follows:
;
; Frequency    ref_osc_3    ref_osc_2    ref_osc_1    ref_osc_0
;
; 120.00 MHz     0x23         0xCA         0x98         0xCE                  
; 100.00 MHz     0x2A         0xF3         0x1D         0xC4                  
;  90.70 MHz     0x2F         0x5A         0x82         0x7A                  
;  66.66 MHz     0x40         0x6E         0x52         0xE7                  
;  66.00 MHz     0x41         0x13         0x44         0x5F                  
;  50.00 MHz     0x55         0xE6         0x3B         0x88                  
;
; To calculate other values: 
;    ref_osc_3 = (2^32 / oscillator_freq_in_Hertz).                           
;    ref_osc_2, ref_osc_1, and ref_osc_0 are the fractional part of           
;     (2^32 / oscillator_freq_in_Hertz) times 2^24.                           
;    Note:   2^32 = 4294967296 and 2^24 = 16777216
;
; For example, for a 120 MHz clock:
;    ref_osc_3 is (2^32 / 120 x 10^6) = 35.791394133 truncated to 35 (0x23)  
;    ref_osc_2 is the high byte of (.791394133 x 2^24) = 13277390.32         
;      13277390.32 = 0xCA98CE, so high byte is CA.                           
;    ref_osc_1 is the next byte of 0xCA98CE, or 98
;    ref_osc_0 is the last byte of 0xCA98CE, or CE


;==== Currently set for 125 MHz Oscillator =======                

ref_osc_3      equ  0x22                ; Most significant osc byte    
ref_osc_2      equ  0x5C                ; Next byte                    
ref_osc_1      equ  0xBC                ; Next byte                    
ref_osc_0      equ  0x00                ; Least significant byte       

; Info for power-up display                                                   
MCODE_REV_0    equ  '2'     	          ; Current code version is 20150312                     
MCODE_REV_1    equ  '0'                                                        
MCODE_REV_2    equ  '1'                                                       
MCODE_REV_3    equ  '5'     
MCODE_REV_4    equ  '0'     
MCODE_REV_5    equ  '3'     
MCODE_REV_6    equ  '1'	  
MCODE_REV_7    equ  '5'     


;***************************************************************************************************
;                                      General definitions                                         *								
;                                                                                                  * 
;***************************************************************************************************

int_0          equ  0x58		               ;The CW Offset (600 Hz)
int_1          equ  0x02		               ;	"
int_2          equ  0x00		               ;	"
int_3          equ  0x00		               ;	"


; **************************************************************************************************
; * Assign names to IO pins.                                                                       *
; **************************************************************************************************
;
;   A Register Bits:
;
;low nibble of PORTA is Band info
rly1	          equ	4
rly2		     equ	5
enc1		     equ	6
enc2		     equ	7
;
;   B register bits:
;
;Low nibble of PORTB is LCD Data
dds_clk	     equ	4
dds_ud	     equ	5
dds_data 	     equ	6
DDS_ioreset	equ	7


; 
;   C register bits:
;
pb1			equ	0
pb2			equ	1
pb3			equ	2
pb4			equ	3
tx			equ	2
LCD_RS   	     equ  4                   ; LCD Mode Setting
LCD_RW   	     equ  5                   ; LCD Read/Write Control
LCD_E    	     equ  6                   ; LCD Enable Bit
DDS_mreset	equ  7		          ; DDS Master_CLear
;
LCD_busy 	     equ  0x03                ; LCD busy bit 
;
;
; ****************************************************************************************************
; *                                      Declare variables                                           *
; ****************************************************************************************************
;
     CBLOCK  0x000                      ; Start Data Block

     freq_0                             ; Display frequency (hex) 
     freq_1                             ;  (4 bytes) 
     freq_2                             ; Used by bin2BCD only
     freq_3         

     freq_0A                            ; Freq info Placeholders 
     freq_1A                            ;  
     freq_2A                            ; 
     freq_3A                            ;

     BCD_0                              ; Display frequency (BCD) 
     BCD_1                              ;  (5 bytes)
     BCD_2                              ;
     BCD_3                              ;
     BCD_4                              ;

     AD9850_0                           ; AD9850 control word 
     AD9850_1                           ;  (5 bytes)
     AD9850_2                           ;
     AD9850_3                           ;
     AD9850_4                           ;

     fstep_0                            ; Frequency inc/dec 
     fstep_1                            ;  (4 bytes)
     fstep_2                            ; update_decade fills these in
     fstep_3                            ; with values from decade_table

     BCD_count                          ; Used in bin2BCD routine
     BCD_temp                           ;   "

     mult_count                         ; Used in calc_dds_word 
     bit_count                          ;   "
     byte2send                          ;

     osc_temp_0                         ; Oscillator frequency 
     osc_temp_1                         ;  (4 bytes)
     osc_temp_2                         ; Used by calibrate
     osc_temp_3

     LCD_char                           ; Character being sent to the LCD
     LCD_read                           ; Character read from the LCD

     timer1                             ; Used in delay routines
     timer2                             ;   "

     encdr_new                          ; New value of encoder pins A and B
     encdr_old                          ; Old value of encoder pins A and B
     encdr_read                         ; Encoder pins A and B and switches
     last_dir                           ; Indicates last direction of encoder
     next_dir                           ; Indicates expected direction

     count                              ; Multipurpose loop counter
	state					     ; Used to tell current state of sideband
     rs_value                           ; The LCD rs line flag value

     PB_flags                           ; Pushbutton flags
     PB_wait_count                      ; PB repeat control timer

     temp                               ; Miscellaneous variable
	tx_status
	TRANSMIT
	UPDT
	VFOcontrol				     ;Used to control Tx vs Rx status
	enc_counter

     decade                             ; Frequency decade subject to updating
     cur_pos                            ; Cursor position: 0-7 for 8 char LCD
  

     vfo_a_0                            ; Used to store the frequency setting
     vfo_a_1                            ; of VFO A when B is in use
     vfo_a_2 
     vfo_a_3

     tbloffst	                         ; Used in table reads
     holder 			               ; temporary storage


     ENDC                               ; End of Data Block


; 
; **************************************************************************************************
; *                  Set a return path in case of program failure                                  * 
; **************************************************************************************************             
;

        ORG     0x0000                
reset_entry
        goto    start                   ; Jump around the band table to main program


; **************************************************************************************************
; * This is the band table.  Each entry is four instructions long, with each                       *
; * group of four literals representing the frequency as a 32 bit integer                          *
; * New entries can be added to the end of the table or between existing                           *
; * entries.  The constant band_end must be incremented by 4 for each entry                        * 
; * added.                                                                                         *                
; *                                                                                                *
; * This table is placed near the top of the program to allow as large a                           *
; * a table as possible to be indexed with the eight bit value in W.                               *
; *                                                                                                *
; * The routine that runs before the actual table is an offset for the                             *
; * 18F2520 processor.  The actual table being called is band_table, the                           *
; * table name bandtbl is used for internal purposes and is not called from                        *
; * the main program.                                                                              *
; *                                                                                                *
; **************************************************************************************************
;
band_table		
			movwf	tbloffst	    ;This routine replaces the original Table counter jump computation.
			bcf		STATUS,C	    ;It must be used because the program counter
			rlcf		tbloffst,F    ;on a 18F2520 counts by twos rather than ones.
			movlw	HIGH(bandtbl)
			btfsc	STATUS,C
			incf		WREG,W
			movwf	PCLATH
			movlw	LOW(bandtbl)
			addwf	tbloffst,W
			btfsc	STATUS,C
			incf		PCLATH,F
			movwf	PCL

bandtbl		retlw     0x80           ; 100,000 Hz
			retlw     0x01           ; 
			retlw     0x86           ;
			retlw     0xA0           ;
			retlw     0x80		     ; 570,000Hz
			retlw     0x08		     ;
			retlw     0xB2		     ;
			retlw     0x90		     ;
			retlw     0x80           ; 1.9MHz
			retlw     0x1C           ; 
			retlw     0xFD           ; 
			retlw     0xE0           ; 
			retlw     0x80           ; 3.56MHz
			retlw     0x36           ; 
			retlw     0x52           ; 
			retlw     0x40           ; 
			retlw     0x00           ; 7.040MHz
			retlw     0x6B           ; 
			retlw     0x6C           ; 
			retlw     0xC0           ; 
			retlw     0x00           ; 10.0MHz
			retlw     0x9A           ; 
			retlw     0x1D           ; 
			retlw     0x20           ;
			retlw     0x00           ; 30 meters
			retlw     0x9A           ; 
			retlw     0x1D           ; 
			retlw     0x20           ; 
			retlw     0x00           ; 30 meters
			retlw     0x9A           ; 
			retlw     0x1D           ; 
			retlw     0x20           ; 
			retlw     0x00           ; WWV
			retlw     0x99           ; 
			retlw     0x96           ; 
			retlw     0x80           ; 
 			retlw     0x00           ; 14.060MHz
			retlw     0xD6           ; 
			retlw     0x89           ; 
			retlw     0xE0           ; 
			retlw     0x01           ; 17 meters
			retlw     0x13           ; 
			retlw     0xB2           ;
			retlw     0x20           ; 
			retlw     0x01           ; 21.060MHz
			retlw     0x41           ; 
			retlw     0x59           ; 
			retlw     0xA0           ; 
			retlw     0x01           ; 12 meters
			retlw     0x7B           ; 
			retlw     0xCA           ; 
			retlw     0x90           ; 
			retlw     0x01           ; 28.060MHz
			retlw     0xAB           ; 
			retlw     0x3F           ; 
			retlw     0x00           ;
			retlw     0x01           ; 29 MHz
			retlw     0xBA           ; 
			retlw     0x81           ; 
			retlw     0x40           ; 
			retlw     0x01           ; 30 MHz
			retlw     0xC9           ;
			retlw     0xC3           ;
			retlw     0x80           ; 


; **************************************************************************************************  
; *                                                                                                *
; *                                   Decade Table                                                 *
; *                                                                                                *
; * Each entry is four instructions long, with each group of four literals representing the        * 
; * frequency as a 32 bit integer. Each four byte entry represents the decade increment which can  *
; * be assigned to fstep_3, fstep_2, fstep_1 and fstep_0. Data is stored MSB first.                *
; *                                                                                                *
; **************************************************************************************************

                                        ; Jump to the appropriate value to return
decade_table	
			movwf     tbloffst	     ;This routine replaces the original Table counter jump computation.
			bcf	     STATUS,C	     ;It must be used because the program counter
			rlcf	     tbloffst,F	;on a 18F2520 counts by twos rather than ones.
			movlw     HIGH(dectbl)
			btfsc     STATUS,C
			incf	     WREG,W
			movwf     PCLATH
			movlw     LOW(dectbl)
			addwf     tbloffst,W
			btfsc     STATUS,C
			incf	     PCLATH,F
			movwf     PCL
dectbl
               retlw     0x00           ; 1Hz increment
               retlw     0x00
               retlw	0x00
               retlw	0x01
               
               retlw	0x00           ; 10 Hz increment
               retlw	0x00
               retlw	0x00
               retlw	0x0a
        
               retlw	0x00           ; 100 Hz increment
               retlw	0x00
               retlw	0x00
               retlw	0x64
        
               retlw	0x00           ; 1 KHz increment
               retlw	0x00
               retlw	0x03
               retlw	0xE8
        
               retlw	0x00           ; 10 KHz increment
               retlw	0x00
               retlw	0x27
               retlw	0x10
        
               retlw	0x00           ; 100 KHz increment
               retlw	0x01
               retlw	0x86
               retlw	0xa0
       
               retlw	0x00           ; 1 MHz increment
               retlw	0x0F
               retlw	0x42
               retlw	0x40
        
              ;retlw	0x00           ; 10 MHz increment
              ;retlw	0x98
              ;retlw	0x96
              ;retlw	0x80
        




; **************************************************************************************************  
; *                                                                                                *
; *                              Cursor Positioning Table                                          *
; *                                                                                                *
; *  8 and 16 character LCD displays differ, so cursor positioning for frequency displays is put   * 
; *  into tables to simplify the software.                                                         *
; *                                                                                                *
; **************************************************************************************************


cursor_table	
               movwf	tbloffst		;This routine replaces the original Table counter jump computation.
               bcf		STATUS,C		;It must be used because the program counter
			rlcf		tbloffst,F	;on a 18F2520 counts by twos rather than ones.
			movlw	HIGH(curtbl)
			btfsc	STATUS,C
			incf		WREG,W
			movwf	PCLATH
			movlw	LOW(curtbl)
			addwf	tbloffst,W
			btfsc	STATUS,C
			incf		PCLATH,F
			movwf	PCL

curtbl              
               retlw	0x8A
			retlw	0x89
			retlw	0x88
               retlw	0x86
			retlw	0x85
			retlw	0x84
               ;retlw	2
               ;retlw	1 
;

int_freq_table  
               movwf	tbloffst		;This routine replaces the original Table counter jump computation.
			bcf		STATUS,C		;It must be used because the program counter
			rlcf		tbloffst,F	;on a 18F2520 counts by twos rather than ones.
			movlw	HIGH(tbl_if)
			btfsc	STATUS,C
			incf		WREG,W
			movwf	PCLATH
			movlw	LOW(tbl_if)
			addwf	tbloffst,W
			btfsc	STATUS,C
			incf		PCLATH,F
			movwf	PCL

tbl_if		retlw	0xFF		     ;100khz
			retlw	0xFF		     ;570kHz
			retlw	0xFF		     ;1.9MHz
			retlw	0xFF		     ;3.5MHz
			retlw	0x00
			retlw	0x00
			retlw	0x00
			retlw	0x00
			retlw	0x00
			retlw	0x00
			retlw	0x00
			retlw	0x00
			retlw	0x00
			retlw	0x00
			retlw	0x00
			retlw	0x00
;
; **************************************************************************************************
; *                                           Program Start                                        *
; **************************************************************************************************
; * Purpose:  This is the start of the program.  It initializes the LCD and detects whether to     *
; *           enter calibrate mode.  If so, it calls the Calibrate routine.  Otherwise, it sets the* 
; *           power-on frequency and enters the loop to poll the encoder.                          *
; *                                                                                                *
; *   Input:  The start up frequency is defined in the default_3 definitions above, and relies on  *
; *           the reference oscillator constant defined in ref_osc_3 ... ref_osc_0.                *
; *                                                                                                *
; *  Output:  Normal VFO operation.                                                                *
; *                                                                                                *
; **************************************************************************************************
;
start
          clrf	     INTCON			; No interrupts for now
          movlw	0x07			     ; Set the 3 LSBs of CMCON
          movwf	CMCON			; to enable normal I/O
          clrf		RCSTA			; Disable USART Receiver

          movlw	0xFF				;set up for ADCON1 to turn off
          movwf	ADCON1		     ;  all ADCs

          clrf	     TXSTA			; Disable USART Transmitter
          clrf	     PIE1			     ; Clear all interrupt enable bits
          clrf	     CVRCON			; Disable voltage reference
          clrf	     T1CON
          movlw     0xCE                ;Set Internal Oscillator for
          movwf     OSCCON              ; 1MHz

          movlw	0x80				;Setup to enable PortB pullups
          movwf	INTCON2			;	 accomplished
          movlw	0xCF				; Set Port A bits. Band & Encoder input, Sideband Relays output
          movwf	TRISA			;
          clrf	     LATA
          clrf	     TRISB			; Set port B to all outputs, LCD Data and DDS
          movlw	0x0F
          movwf	TRISC			;Set PORTC low nibble Inputs for PB1-3, outputs for LCD & DDS
	
	;call      wait_256ms
          clrf      PORTB
     call      init_LCD                 ; Initialize the LCD
     call      display_version          ; Display title and version number
	call	     init_DDS				;initialize the DDS


          movff	PORTA, holder		; read band info from port A
          movlw	0x0F				; "set up a band table read mask"
          andwf	holder			; apply mask to hide the upper bits
          rlncf	holder			; Reading must be multiplied by 4 
          rlncf	holder			; to read properly from table 

	call 	get_band

	call    send_dds_word	          ; Send the power-on frequency to the
                                        ;   AD9850 in serial mode
	call    send_dds_word	          ; Send it twice. Sometimes needed for
                                        ;   a clean start-up

          movlw	0x86
          movwf	cur_pos
          movlw	0x03
          movwf	decade
 	call    	show_freq                ; COnvert to BCD and display it
          clrf		fstep_3			;pre-load fstep values
          clrf		fstep_2			;	"
          movlw	0x03
          movwf	fstep_1			;	"
          movlw	0xE8				;	"
          movwf	fstep_0			;	"

	call 	wait_16ms
          clrf    last_dir              ; Clear the knob direction indicator

; Write "RCV" to LCD
          movlw     0xC0                ; Point LCD at digit 9 of 2nd line
     call    cmnd2LCD                   ;
          movlw     'R'                 ; Say "RCV "
     call    data2LCD                   ;
          movlw     'X'                 ; 
     call    data2LCD                   ;
 
          movlw     0xC8            	; Point LCD at digit 8 of 2nd line
     call    cmnd2LCD                   ;
          movlw     'L'                 ; Send "USB"
     call    data2LCD                   ;
          movlw     'S'                 ; 
     call    data2LCD                   ;
          movlw     'B'                 ; 
     call    data2LCD                   ;
 
          bcf		VFOcontrol,TRANSMIT



; **************************************************************************************************
; *                                      Main Program Loop                                         *
; **************************************************************************************************
; *                                                                                                *
; *           This is the Main Program Loop. The program's main loop calls poll_encoder, which     *
; *           continuously polls the rotary shaft encoder.  When the shaft encoder has changed, the*
; *           direction it moved is determined and stored in last_dir.  The subroutine then returns*
; *           to main.                                                                             *                       
; *                                                                                                *
; *           If the tuning-increment pushbutton (pb1) is 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 poll_encoder 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 show_freq 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.  *
; *                                                                                                *
; **************************************************************************************************
;

main

 
	call      poll_encoder             ; Check for knob movement (wait there!)
                                        ; Return here when encoder change detected

     call      step                     ; No, just step    


     goto      main

alt_update
     call      show_freq                ; Display the new frequency on the LCD
     call      send_dds_word            ; Send the new control word to the DDS chip
          return	
;***************************************************************************************************
; *                                             Step Functions                                     *
; **************************************************************************************************
; *                                                                                                *
; * Adjusts the frequency by examining the last_dir value and either adds or subtracts the value of*
; * fstep_3..0 from the current frequency setting.                                                 *
; *                                                                                                *
;***************************************************************************************************

step                                    ; Based on the knob direction, either add or
                                        ; subtract the increment, then update the
                                        ; LCD and DDS.
          btfsc     last_dir,6          ; Is the knob going up?
     goto      up                       ; Yes, then add the increment

down                                    ; Else, shaft direction is CCW
     call      sub_step                 ; Subtract fstep from freq
     goto      update                   ; Update LCD and DDS

up
     call      add_step                 ; Add fstep to freq


update
     call      show_freq                ; Display the new frequency on the LCD
     call      send_dds_word            ; Send the new control word to the DDS chip

     return		                    ; Continue main loop


;
; **************************************************************************************************
; *                                       Get Band                                                 *
; **************************************************************************************************
; *  This routine reads the frequency value of a band table entry pointed to by band and returns   *
; *  it in freq_3...freq_0.                                                                        *
; **************************************************************************************************
;
get_band
		movf      holder,w            ; Get the index of the high byte 
	call    band_table                 ; Get the value into W
		movwf     freq_3              ; Save it in freq_3
		bcf		freq_3,7			;clear the MSD.  It's used for IF Offset only.
		incf      holder,f            ; Increment index to next byte
		movf      holder,w            ; Get the index of the next byte
	call    band_table                 ; Get the value into W
		movwf     freq_2              ; Save it in freq_2
		incf      holder,f            ; Increment index to the next byte
		movf      holder,w            ; Get the index to the next byte
	call    band_table                 ; Get the value into W
		movwf     freq_1              ; Save it in freq_1
		incf      holder,f            ; Increment index to the low byte
		movf      holder,w            ; Get the index to the low byte
	call    band_table                 ; Get the value into W
		movwf     freq_0              ; Save it in freq_0
		movlw     0x03                ; Get a constant three
		subwf     holder,f            ; Restore original value of band
	return                             ; Return to the caller


; **************************************************************************************************
; *	                                      Add Step                                                *
; **************************************************************************************************
; *	This routine adds the 32 bit value of fstep to the 32 bit value in freq.                      *
; *  Borrowed from:  <http://www.pic-projects.net/index.php?option=com_content&view=article&id=    *
; *       57:multi-byte-addition-a-subtraction&catid=38:arithmetic&Itemid=57>                      *
; **************************************************************************************************

add_step


          bcf	     STATUS, C		     ;clear the carry bit
	   
          movf	     fstep_0,w
          addwf     freq_0,
          movf	     fstep_1,w
          addwfc    freq_1,
          movf	     fstep_2,w
          addwfc    freq_2,F
          movf	     fstep_3,w
          addwfc    freq_3,F 
	   
     return



; **************************************************************************************************
; *                                       Sub_Step                                                 *
; **************************************************************************************************
; *  Subtract the increment step from freq, checking that it does not go below zero.               *
; **************************************************************************************************

sub_step

		movf	     fstep_0,w
		subwf	freq_0,f
		movf	     fstep_1,w
		subwfb	freq_1,f
		movf	     fstep_2,w
		subwfb	freq_2,f
		movf	     fstep_3,w
		subwfb	freq_3,f

	return
 
;
; **************************************************************************************************
; *                                       Poll Encoder                                             *
; **************************************************************************************************
; *  This routine does the following:                                                              *
; *       1. Reads the encoder bits until a change is detected, then determines the direction the  *
; *             knob was moved.                                                                    *
; *       2. Performs mechanical shaft encoder debounce and detent processing.                     *
; *       3. Reads pushbuttons and calls their appropriate subroutines.                            *
;***************************************************************************************************
;

poll_encoder

		btfsc     VFOcontrol,UPDT     ; Need a DDS and LCD update? 
	call	          alt_update		;Update the DDS and LCD
	
	call	          PB1				;We aren't transmitting, process normally
	call	          PB2
	call 	     PB3
	;call		PB4
	call	          crsr
	
		movff    	PORTA,encdr_old     ; Get the current encoder value place it into "old"
		movlw	0xC0			     ; Setup a mask
		andwf	encdr_old		     ;mask off unimportant bits

  	call	          wait_1ms

		movff    	PORTA,encdr_new     ; Get the current encoder value place it into "old"
		movlw	0xC0				; Setup a mask
		andwf	encdr_new			;mask off unimportant bits
		movf	     encdr_new,w

          cpfseq	encdr_old			;compare W and Encdr_old, skip if they're equal
		
	goto	          next				; they aren't equal, so proceed
     goto	          poll_encoder        ; they are equal, there's been no change, return to main and do it again
                                
; It changed. Now determine which direction the encoder turned.

;=============================================================================P
;   Encoder bits are on RA0 and RA1 - the two low order bits of ren_new       P  
;   A and B are "gray code" - 90 degrees out of phase (quadrature)            P
;         ___     ___                                                         P
;        |   |   |   |                                                        P
; A  ____|   |___|   |___                                                     P
;           ___     ___                                                       P
;          |   |   |   |                                                      P
; B     ___|   |___|   |___                                                   P
;       ^ ^ ^ ^ ^ ^ ^ ^                                                       P
;       a b c d a b c d                                                       P
;                                                                             P
;              A B                                                            P
; At point a:  0 0                                                            P
; At point b:  1 0                                                            P
; At point c:  1 1                                                            P
; At point d:  0 1                                                            P
;                                                                             P
; Going UP, the sequence is a,b,c,d,a,b,c,d, etc. so the sequence is:         P
;     00, 10, 11, 01, 00, 10, 11, 01, etc.                                    P
;                                                                             P
; Going DOWN, the sequence is d,c,b,a,d,c,b,a, etc. so the sequence is:       P
;     01, 11, 10, 00, 01, 11, 10, 00, etc.                                    P
;                                                                             P
; To determine if the sequence is UP or DOWN:                                 P
;   1) Take the "Right-Bit" of any pair.                                      P
;   2) XOR it with the "Left-Bit" of the next pair in the sequence.           P
;   3) If the result is 1 it is UP                                            P
;      If the result is 0 it is DOWN                                          P
;                                                                             P
; The direction flag is 0 (DOWN) or 2 (UP) because of bit positioning         P
;=============================================================================P
next
          bcf       STATUS,C            ; Clear the carry bit to prepare for rotate 
          rrncf     encdr_new,f         ; Rotate old bits left to align "Right-Bit" 
          movf      encdr_old,w         ; Set up new bits in W                      
          xorwf     encdr_new,f         ; XOR old (left shifted) with new bits      
          movf      encdr_new,w         ; Put XOR results into W also               
          andlw     0x40                ; Mask to look at only " right Bit" of pair   
          movwf     next_dir            ; Save result (in W) as direction (bit=UP)  
          xorwf     last_dir,w          ; See if direction is same as before        

;
;       Prevent encoder slip from giving a false change in direction.
;

          btfsc     STATUS,Z            ; Zero flag set? (i.e, is direction same?)  
     goto      pe_continue         ; Yes, same direction so no slip; keep going
          movf      next_dir,w          ; No Zero-flag, so direction changed        
          movwf     last_dir            ; Update the direction indicator            
          movf      encdr_new,w         ; Save the current encoder bits (now in W)  
          movwf     encdr_old           ;   for next time                           
     goto      poll_encoder        ; Try again

pe_continue
          clrf      last_dir            ; Clear last_dir (default is DN)            
          btfsc     next_dir,6          ; Are we going UP?                          
     goto           pe_up               ; Yes, go process it.
                                        ; Else, we are goiong down

          movf      encdr_new,w         ; Get the current encoder bits
          movwf     encdr_old           ; Save them in ren_old for the next time
          decf	     enc_counter,f       ; decrement the inter-detent counter
          btfsc	enc_counter,0       ; Check if bit 0 is cleared
     goto	          poll_encoder        ; If not, then poll some more
          btfsc	enc_counter,1       ; Else, check if bit 1 is cleared
     goto	          poll_encoder        ; If not, then poll some more
                                        ; Else, assume the encoder is on a detent

     goto           pe_movement         ; Indicate that the encoder has moved
pe_up 
          movlw     0x40                ; Get UP value
          movwf     last_dir            ;   and set in last_dir
          movf      encdr_new,w         ; Get the current encoder bits
          movwf     encdr_old           ; Save them in ren_old for the next time
          decf	     enc_counter,f       ; decrement the inter-detent counter
          btfsc	enc_counter,0       ; Check if bit 0 is cleared
     goto	          poll_encoder        ; If not, then poll some more
          btfsc	enc_counter,1       ; Else, check if bit 1 is cleared
     goto	          poll_encoder        ; If not, then poll some more
                                        ; Else, assume the encoder is on a detent

pe_movement                             ; Arrive here when encoder is being turned
          movff     PORTA,encdr_new     ; Get the current encoder value place it into "old"
		movlw	0xC0				; Setup a mask
		andwf	encdr_new			;mask off unimportant bits
          movff     encdr_new,encdr_old ; Get the current encoder bits
      goto           pe_exit             ; Finish up
          
pe_exit
     return                             ; Return to the caller


;*****************************************************************************************************
;                                       Pushbuttons                                                  *
;*****************************************************************************************************                           
; *   The following subroutines contain the code to control the functions of the 4 pushbuttons.      *
; *     The functions are:                                                                           *
; *            -PB1 - Tuning Decade Select                                                           *
; *            -PB2 - USB/LSB Select                                                                 *
; *            -PB3 - SSB Transmit                                                                   *
; *            -PB4 - CW Transmit                                                                    *
;*****************************************************************************************************
PB1
	     movff	PORTC, temp		;is PB1 pressed?
	     btfsc	temp,pb1
	goto	          pb_exit			; No.  Exit this subroutine
	call	          wait_1ms
	     movff	PORTC,temp
	     BTFSS	temp, pb1		     ; Yes.  Has it been released yet?
    goto 	          PB1				;   No.  Wait for PB1 to be released
	     incf	     decade			;   Yes, increment the decade value
	     movlw	0x06			     ; Load the "highest_decade" comparison value
	     cpfslt	decade			; Does the current decade value exceed the highest value?
	     clrf	     decade			; Yes.  Reset the decade value
	;clrf	holder			     ;clear a temporary register
crsr
	     movff	decade,holder	     ; No. accept the value and place it into "holder"

	     movf	     decade,w		     ;prepare to read the proper  cursor position
	call	          cursor_table		;read the position
	     movwf	cur_pos			;store the cursor position info
 
	     rlcf		holder			; multiply holder by 4 for the table read
	     rlcf		holder			;  "
	     movf		holder,w			;move holder into w for the table read
	call           decade_table		; table read
	     movwf	fstep_3			; move the obtained value into Step_3
	     incf		holder			;increment holder
	     movf		holder,w			;move holder into w for the next table read
	call	          decade_table		; next table read
	     movwf	fstep_2			;move obtained value into step_2
	     incf		holder			;increment holder for next table read
	     movf		holder,w			;move holder into w for the next table read
	call	          decade_table		;call the table read
	     movwf	fstep_1			;move the results into step_1
		incf		holder			;increment holder
		movf		holder,w			;move to w for the next table read
	call	          decade_table		;perform the final read
		movwf	fstep_0			;move the results into step_0

		movf		cur_pos,w
	call   	     cmnd2LCD         	;  Display the new cursor position on the LCD	
	return
pb_exit	
	return
	

;***************************************************************************************************
; *                                      Sideband Select (PB2)                                     *
; **************************************************************************************************
; * Check status of PB2.  If set, change state of a Sideband bit and based on that state, change   *
; * the two sideband relay bits This pulses out either bits "rly1" and "rly 2", with the other one *
; * clear/grounded.                                                                                *
; **************************************************************************************************

PB2
		movff	PORTC, temp		;Put the contents of PORTC into Temp for the PB read
		btfsc	temp,pb2			;Negative logic.  Clear means the switch is pushed. Is it pushed?
	goto		     pb2_exit				; No.  Exit this subroutine
		call		wait_64ms			; Yes, wait briefly
		movff	PORTC,temp		; Transfer for another reading
		BTFSS	temp, pb2			; Has it been released yet?
	goto 	     PB2				;   No.  Wait for PB2 to be released
		btfss	state,7   		;   Yes, is the current sideband upper, or lower?
	goto		     set_sideband		; Upper, change  it to Lower
	goto		     reset_sideband		; Lower, change it to Upper.
	
set_sideband	
          bsf		state,7			;Set the Sideband-state flag to Lower
          bcf		LATA,rly2			;Ground one side of the relay
          bsf		LATA,rly1			;Set the other side high
	call		     wait_64ms			;Brief delay
          bcf		LATA,rly1			;reset the high side of the relay

;Display the state of the Sideband on the LCD Lower line	
          movlw     0xC8            	; Point LCD at digit 8 of 2nd line
     call           cmnd2LCD            ;
          movlw     'L'                 ; Send "LSB"
     call           data2LCD            ;
          movlw     'S'                 ; 
     call           data2LCD            ;
          movlw     'B'                 ; 
     call           data2LCD            ;
	goto		     pb2_exit
	
reset_sideband
          bcf		state,7			;Put the state ofhte sideband flag to Lower
          bcf		LATA,rly1			;Ground the "other" side of the relay
          bsf		LATA,rly2			;Set the "other-other" side of the relay high
	call		     wait_64ms			;Hold it there briefly
          bcf		LATA,rly2			;Ground boht sides of the relay

;Display the state of the Sideband on the LCD Lower line		
          movlw     0xC8                ; Point LCD at digit 8 of 2nd line
     call           cmnd2LCD            ;
          movlw     'U'                 ; Send "USB"
     call           data2LCD            ;
          movlw     'S'                 ; 
     call           data2LCD            ;
          movlw     'B'                 ; 
     call           data2LCD            ;
	
pb2_exit
	return

; **************************************************************************************************
;                                      PB3 SSB Transmit (PB3)                                      *
; **************************************************************************************************
; *  PB3 is the SSB Transmit sense line.  The only thing it does when activated is display         *
; *   "Tx" on the LCD.                                                                             *
; **************************************************************************************************
PB3
		movff	PORTC, temp	     ;Put the contents of PORTC into Temp for the PB read
		btfsc	temp,pb3		     ;Negative logic.  Clear means the switch is pushed. Is it pushed?
	goto	          pb3_rx			;  No.  Exit this subroutine
	
pb3_tx	;Display the state of the Sideband on the LCD Lower line		
		movlw     0xC0                ; Point LCD at digit 8 of 2nd line
     call           cmnd2LCD          	;
		movlw     'T'                 ; Send "Tx"
     call           data2LCD          	;
		movlw     'X'                 ; 
     call           data2LCD          	;
	goto           pb3_exit
	
pb3_rx	;Display the state of the Sideband on the LCD Lower line		
		movlw     0xC0            	; Point LCD at digit 8 of 2nd line
     call           cmnd2LCD          	;
		movlw     'R'                 ; Send "Rx"
     call           data2LCD          	;
		movlw     'X'                 ; 
     call           data2LCD          	;
     
pb3_exit
  	return
; **************************************************************************************************
;                                        PB CW Transmit (PB4)                                      *
; **************************************************************************************************
; *  PB4 is the CW Transmit sense line.  It display s "Rx" when activated, and it adds a CW        *
; *   offset to its frequency to enable proper CW receive.                                         *
; **************************************************************************************************
PB4  
 
	return
;**************************************************************************************************
; *					     Process Intermediate Frequency (IF)					        *
; *************************************************************************************************
; *  For a CW transceiver, the transmit signal must be offset by sidetone frequesncy.  Thus, if I *
; *  display the actual DDS frequency, it  would be off by the IF.  This offset adjusts for that. *
; *  When done, either the Preset IF is added to, or subtracted from the DDS frequeny             *
; *************************************************************************************************

process_int
          movff	freq_0,vfo_a_0
          movff	freq_1,vfo_a_1
          movff	freq_2,vfo_a_2
          movff	freq_3,vfo_a_3
	
          movff	PORTA, holder	     ; read band info from port A
          movlw	0x0F			     ; Set up a band table read mask
          andwf	holder		     ; apply mask to hide the upper bits
          rlncf	holder		     ; Reading must be multiplied by 4 
          rlncf	holder		     ; to read properly from table 
	
	call	          int_freq_table		;call the IF table
          movwf	holder		     ;transfer the table read into holder

          tstfsz	holder
	goto	          add_if			;bit is set, add the IF
	goto	          sub_if			;is clear, subtract the IF

add_if	
	   bcf	     STATUS, C		     ;clear the carry bit
	   
        movlw	     int_0
        addwf       vfo_a_0,f
        movlw	     int_1
        addwfc      vfo_a_1,f
        movlw	     int_2
        addwfc      vfo_a_2,f
        movlw	     int_3
        addwfc      vfo_a_3,f 
		   
	   return				          ;go to the place in "calc_dds_word" where the
                                        ; transmit status is tested, and IF is processed or not.
sub_if

		movlw	int_0
		subwf	vfo_a_0,f
		movlw	int_1
		subwfb	vfo_a_1,f
		movlw	int_2
		subwfb	vfo_a_2,f
		movlw	int_3
		subwfb	vfo_a_3,f

	   return				          ;go to the place in "calc_dds_word" where the
                                        ; transmit status is tested, and IF is processed or not.
;
; **************************************************************************************************
; *	                                      Calc DDS Word                         			    *
; **************************************************************************************************
; * This routine multiplies the 32-bit number for oscillator frequency times the 32-bit number for *	
; * the displayed frequency.  The result is stored in AD9851_3 ... AD9851_0.                       *
; **************************************************************************************************
;
calc_dds_word
		clrf	   	AD9850_0		     ; Clear the AD9850/AD9851 control word bytes
		clrf	   	AD9850_1		     ; 
		clrf	   	AD9850_2		     ; 
		clrf	   	AD9850_3		     ; 
		clrf	   	AD9850_4		     ;

	call		     process_int	     ;Call sub to compute the I.F. offset (if any)

retn_to_word
		clrf		osc_temp_3
		clrf		osc_temp_2
		clrf		osc_temp_1
		clrf		osc_temp_0
		movlw   	0x20			     ; Set count  to 32	  (4 osc bytes of 8 bits)
		movwf  	mult_count		; Keep running count
	
		movlw	ref_osc_3
		movwf	osc_temp_3

		movlw	ref_osc_2
		movwf	osc_temp_2

		movlw	ref_osc_1
		movwf	osc_temp_1
	
		movlw	ref_osc_0
		movwf	osc_temp_0

mult_loop
		bcf	   	STATUS,C		     ; Start with Carry clear
		btfss   	osc_temp_0,0	     ; Is bit 0 (Least Significant bit) set?
	goto	   	     noAdd			; No, don't need to add freq term to total
		movf		vfo_a_0,w		     ; Get the "normal" freq_0 term
 
		addwf     AD9850_1,f		; Add it in to total
		btfss   	STATUS,C		     ; Does this addition result in a carry?
	goto	   	     add7			 	; No, continue with next freq term
		incfsz  	AD9850_2,f		; Yes, add one and check for another carry
	goto	   	     add7			 	; No, continue with next freq term
		incfsz  	AD9850_3,f		; Yes, add one and check for another carry
	goto	   	     add7			 	; No, continue with next freq term
		incf	   	AD9850_4,f		; Yes, add one and continue
add7
		movf	     vfo_a_1,w		 	; Get the "normal" freq_0 term
 
		addwf     AD9850_2,f		; Add freq term to total in correct position
		btfss   	STATUS,C		     ; Does this addition result in a carry?
	goto	   	     add8			 	; No, continue with next freq term
		incfsz  	AD9850_3,f		; Yes, add one and check for another carry
	goto	   	     add8			 	; No, continue with next freq term
		incf	   	AD9850_4,f		; Yes, add one and continue
add8
		movf	     vfo_a_2,w		 	; Get the "normal" freq_2 term
		addwf     AD9850_3,f		; Add freq term to total in correct position
		btfss   	STATUS,C		     ; Does this addition result in a carry?
	goto	   	     add9			 	; No, continue with next freq term
		incf	   	AD9850_4,f		; Yes, add one and continue
add9
		movf	     vfo_a_3,w		 	; Get the "normal" freq_3 term

		addwf     AD9850_4,f		; Add freq term to total in correct position
noAdd
		rrcf	     AD9850_4,f		; Shift next multiplier bit into position
		rrcf	     AD9850_3,f		; Rotate bits to right from byte to byte
		rrcf	     AD9850_2,f		; 
		rrcf	     AD9850_1,f		; 
		rrcf	     AD9850_0,f		; 
		rrcf	     osc_temp_3,f,1	  	; Shift next multiplicand bit into position
		rrcf	     osc_temp_2,f,1	  	; Rotate bits to right from byte to byte
		rrcf	     osc_temp_1,f,1	  	; 
		rrcf	     osc_temp_0,f,1	  	; 
		decfsz    mult_count,f	 	; One more bit has been done.  Are we done?
	goto	   	     mult_loop		 	; No, go back to use this bit
		clrf      AD9850_4		     ; Last byte to be sent					
								; Mult answer is in bytes _3 .. _0		    
	return				 		; Done.

;*****************************************************************************************************
; *                                         Initialize DDS                                           *
; ****************************************************************************************************
; *                                                                                                  *
; * This subroutine initializes the AD9854 by sending a series of words that contain the DDS         *
; *    internal registry settings to set things like clock multiplier and the phase between outputs  *
;*****************************************************************************************************
;
init_DDS

        call    wait_ten_ops          ;                                    
;
; Pulse the Master Reset to initialize the DDS chip.
;
        bsf     LATC,DDS_mreset  ; Raise the master reset
        call    wait_ten_ops          ; Wait a long time                  
        bcf     LATC,DDS_mreset  ; Drop the master reset
        call    wait_ten_ops          ; Wait a long time                   
;
; Now pulse the IO reset 
;
        bsf     LATB,DDS_ioreset ; Raise the IO reset
        call    wait_ten_ops          ; Wait a long time                   
        bcf     LATB,DDS_ioreset ; Drop the IO reset
;
; Send the command to Write the CR register.
;
        movlw   0x07	         ; Get the write CR bytes command
        movwf   byte2send         ; Pass it
        call    send_DDS_byte     ; Send it                            
;
; Send the four CR bytes.
;
        movlw   0x10         ; Get the first DDS CR byte
        movwf   byte2send         ; Pass it
        call    send_DDS_byte     ; Send it                            
        movlw   0x20         ; Get the second DDS CR byte
        movwf   byte2send         ; Pass it
        call    send_DDS_byte     ; Send it                           
        movlw   0x00         ; Get the third DDS CR byte
        movwf   byte2send         ; Pass it
        call    send_DDS_byte     ; Send it        
        movlw   0x40         ; Get the 4th DDS CR byte
        movwf   byte2send         ; Pass it
        call    send_DDS_byte     ; Send it               
;  
;       External Update is now set. The 4th byte probably not sent,
;         so need a new IO Reset and new command
;
; Now pulse the IO reset to start a new command
;
        bsf     LATB,DDS_ioreset ; Raise the IO reset
        call    wait_ten_ops          ; Wait a long time   
        bcf     LATB,DDS_ioreset ; Drop the IO reset
;
; Send the command to Write the CR register again.
;
        movlw   0x07      ; Get the write CR bytes command
        movwf   byte2send         ; Pass it 
        call    send_DDS_byte     ; Send it                
;
; Send the four CR bytes again.
;
        movlw   0x10         ; Get the first DDS CR byte
        movwf   byte2send         ; Pass it
        call    send_DDS_byte     ; Send it                      
        movlw   0x20         ; Get the second DDS CR byte
        movwf   byte2send         ; Pass it
        call    send_DDS_byte     ; Send it                  
        movlw   0x00         ; Get the third DDS CR byte
        movwf   byte2send         ; Pass it
        call    send_DDS_byte     ; Send it                   
        movlw   0x40         ; Get the 4th DDS CR byte
        movwf   byte2send         ; Pass it
        call    send_DDS_byte     ; Send it                     
;
; Now we need to send external update to make the entire Init "take"
;
        bsf     LATB,dds_ud    ; Raise the IO update
		call	wait_ten_ops
        bcf     LATB,dds_ud    ; Drop the IO update

        return                    ; Return to the caller
;
;***************************************************************************************************
; *                                     send_dds_freq                                              *
; ************************************************************************************************** 
; * This routine sends the AD9854 control word to the DDS chip using a serial data transfer.       *
; *                                                                                                *
;***************************************************************************************************
;
send_dds_word                           ; Send the command word and the frequency value.

	call calc_dds_word
;
          bsf       LATB,DDS_ioreset    ; Pulse the I/O reset line
          nop				          ; Wait 10 instructions
	call	wait_ten_ops
          bcf       LATB,DDS_ioreset    ;
;
          movlw     0x02                ; Get the command byte to load frequency
          movwf     byte2send           ; Pass it
     call    send_DDS_byte              ; Send it
          movf      AD9850_3,w          ; Get the most significant byte
          movwf     byte2send           ; Pass it
     call    send_DDS_byte              ; Send it
          movf      AD9850_2,w          ; Get the next byte
          movwf     byte2send           ; Pass it
     call    send_DDS_byte              ; Send it
          movf      AD9850_1,w          ; Get the next byte
          movwf     byte2send           ; Pass it
     call    send_DDS_byte              ; Send it
          movf      AD9850_0,w          ; Get the least significant byte
          movwf     byte2send           ; Pass it
     call    send_DDS_byte              ; Send it
          movlw     0x00                ; Get zero for last two bytes
          movwf     byte2send           ; Pass it
     call    send_DDS_byte              ; Send it
     call    send_DDS_byte              ; Send it again
;
          bsf       LATB,dds_ud         ; Raise the IO update
     call	wait_ten_ops
          bcf       LATB,dds_ud         ; Drop the IO update
;
     return                             ; Return to the caller
;
;
;*****************************************************************************************************
; *                                            send_DDS_byte                                         *
; ****************************************************************************************************
; * This subroutine sends a byte, most significant digit first, to the AD9854 through the serial     *
; * interface.                                                                                       *
;*****************************************************************************************************
;
send_DDS_byte
;
        movlw   0x08              ; Get the loop count
        movwf   bit_count         ; Set counter
next_DDS_bit
        bsf     LATB,dds_data    ; Guess we are sending a one
        rlcf    byte2send,f       ; Test if next bit is 1 or zero
        btfss   STATUS,C          ; Was it zero?
        bcf     LATB,dds_data    ; Yes, clear data line
        call	wait_ten_ops        
        bsf     LATB,dds_clk     ; Toggle write clock
        call	wait_ten_ops
        bcf     LATB,dds_clk     ;
;
        decfsz  bit_count,f       ; Has the whole byte been sent?
        goto    next_DDS_bit      ; No, keep going
        return


; *****************************************************************************************************
; *                                       Init_LCD                       						  *
; *****************************************************************************************************
; * Purpose:  Power on initialization of Liquid Crystal Display.  The LCD controller chip must be     *
; *           a Hitachi 44780 or equivalent.  The LCD is assumed to be a 16 x 2.                      *
; *****************************************************************************************************
;
init_LCD
                    bcf     LATC,LCD_RW
                    bcf     LATC,LCD_RS
                    bcf     LATC,LCD_E
				call    wait_64ms        	 ; Wait for LCD to power up
										; Put 4-bit command inRB3..RB0                                         
										;PIC's RB3..RB0 lines connect to LCD's DB7..DB4 (pins 14-11)            
				movlw   0x03              	; LCD init instruction (First)               
				movwf   PORTB             	; Send to LCD via RB7..RB4                   
				bsf     LATC,LCD_E       ; Set the LCD E line high,
				call    wait_32ms         ;   wait a "long" time,
				bcf     LATC,LCD_E       ;   and then Clear E 
				
				movlw   0x03              ; LCD init instruction (Second)              
				movwf   PORTB             ; Send to LCD                   
				bsf     LATC,LCD_E       ; Set E high,
				call    wait_32ms         ;   wait a while,
				bcf     LATC,LCD_E       ;   and then Clear E 
				
				movlw   0x03              ; LCD init instruction (Third)               
				movwf   PORTB             ; Send to LCD                   
				bsf     LATC,LCD_E       ; Set E high,
				call    wait_32ms         ;   wait a while,
				bcf     LATC,LCD_E       ;   and then Clear E
				
				movlw   0x02              ; 4-bit mode instruction                     
				movwf   PORTB             ; Send to LCD via RB3..RB0                   
				bsf     LATC,LCD_E       ; Set E high,
				call    wait_32ms         ;   wait a while,
				bcf     LATC,LCD_E       ;   and then Clear E
				
				movlw	0x28						; 2 line, 5x7 matrix
			call	cmnd2LCD
				
				movlw	0x08						;Turn Display Off
			call	cmnd2LCD				

				movlw	0x01						; Clear Display
			call	cmnd2LCD

				movlw	0x06						; Write from Left to right, each
			call	cmnd2LCD						;  write advanced address by 1
				
				movlw	0x0E						; Turn Display on with underline Cursor
			call	cmnd2LCD

				return                    ; 
;
;
; *****************************************************************************************************
; *                                         display_version                                           *
; *****************************************************************************************************     
; * This subroutine displays version and other info on LCD for 2 seconds after power-up               *
; *           upon power-up                                                                           *  
; *****************************************************************************************************
;
display_version

				;movlw   0x80              ; Point LCD at digit 1                
				;call    cmnd2LCD          ;
				movlw   'R'               ; digit 1              
				call    data2LCD          ;
				movlw   '2'               ; digit 2                  
				call    data2LCD          ;
				movlw   '/'               ; digit 3                
				call    data2LCD          ;
				movlw   'T'               ; digit 4               
				call    data2LCD          ;
				movlw   '2'               ; digit 5              
				call    data2LCD          ;        
				movlw   ' '               ; digit 6              
				call    data2LCD          ;
				movlw   ' '               ; digit 7               
				call    data2LCD          ;
				movlw   ' '               ; digit 8               
				call    data2LCD          ;


				movlw   MCODE_REV_0       ; Get mcode rev byte
				call    data2LCD          ;   and display it (digit 9)
				movlw   MCODE_REV_1       ; Get mcode rev byte
				call    data2LCD          ;   and display it (digit 10)
				movlw   MCODE_REV_2       ; Get mcode rev byte
				call    data2LCD          ;   and display it (digit 11)
				movlw   MCODE_REV_3       ; Get mcode rev byte
				call    data2LCD          ;   and display it (digit 12)
				movlw   MCODE_REV_4       ; Get mcode rev byte
				call    data2LCD          ;   and display it (digit 13)
				movlw   MCODE_REV_5       ; Get mcode rev byte
				call    data2LCD          ;   and display it (digit 14)
				movlw   MCODE_REV_6       ; Get mcode rev byte
				call    data2LCD          ;   and display it (digit 15)
				movlw   MCODE_REV_7       ; Get mcode rev byte
				call    data2LCD          ;   and display it (digit 16)
		call	wait_512ms

				return

; *****************************************************************************************************
; *	                                         bin2BCD                                                 *
; *****************************************************************************************************
; * 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

				movlw	0x20
				movwf	BCD_count
				clrf	   WREG
				clrf	   BCD_0
				clrf	   BCD_1
				clrf	   BCD_2
				clrf	   BCD_3
				clrf	   BCD_4

				clrf	freq_0A
				clrf	freq_1A
				clrf	freq_2A
				clrf	freq_3A

				movff	freq_0, freq_0A
				movff	freq_1, freq_1A
				movff	freq_2, freq_2A
				movff	freq_3, freq_3A

bcdloop	

				rlcf	freq_0A
				rlcf	freq_1A
				rlcf	freq_2A
				rlcf	freq_3A

				movf	BCD_0,w
				addwfc	BCD_0,w
				daw
				movwf	BCD_0

				movf	BCD_1,w
				addwfc	BCD_1,w
				daw
				movwf	BCD_1

				movf	BCD_2,w
				addwfc	BCD_2,w
				daw
				movwf	BCD_2

				movf	BCD_3,w
				addwfc	BCD_3,w
				daw
				movwf	BCD_3

				movf	BCD_4,w
				addwfc	BCD_4,w
				daw
				movwf	BCD_4

				decfsz	BCD_count
				bra		bcdloop
				return	

;
; **************************************************************************************************
; *                                        show_freq                                               *
; **************************************************************************************************
; * This subroutine displays the frequency on the LCD in the format:    14,025,000 Hz              *
; *                                                                                                *
; **************************************************************************************************
;
show_freq

		call bin2BCD
				movlw   0x80        ; Point the LCD to first LCD digit location  
				call    cmnd2LCD    ; Send starting digit location to LCD

				movlw   ' '         ; Send a space
				call    data2LCD    ;   to position 1 of LCD

;
; Running 4-bit mode, so need to send Most Significant Nibble first.
;
; Extract and send "XXXX" from byte containing "XXXXYYYY"
;  - Swap halves to get YYYYXXXX
;  - Mask with 0x0F to get 0000XXXX
;  - Add ASCII bias (0030XXXX)
;
				swapf   BCD_3,w     ; Swap 10MHz BCD digit into lower nibble of W
				andlw   0x0F        ; Mask for lower nibble only       (0000XXXX)
				addlw   0x30        ; Add offset for ASCII char set    (0030XXXX)
				call    data2LCD    ; Send byte in W to LCD
;
; Extract and send "YYYY" from byte containing "XXXXYYYY"
;   - Mask with 0x0F to get 0000YYYY
;   - Add offset for ASCII character set in LCD  (0030YYYY)
;
				movf    BCD_3,w      ; Put 1MHz BCD digit into lower nibble of W
				andlw   0x0F         ; Mask for lower nibble only       (0000YYYY)
				addlw   0x30         ; Add offset for ASCII char set    (0030YYYY)
				call    data2LCD     ; Send byte in W to LCD
 

				movlw   ','         ; Get a comma
				call    data2LCD    ; Send byte in W to LCD


				swapf   BCD_2,w     ; Swap 100KHz BCD digit into lower nibble of W
				andlw   0x0F        ; Mask for lower nibble only       (0000XXXX)
				addlw   0x30        ; Add offset for ASCII char set    (0030XXXX)
				call    data2LCD    ; Send byte in W to LCD
;
				movf    BCD_2,w     ; Put 10KHz BCD digit into lower nibble of W
				andlw   0x0F        ; Mask for lower nibble only       (0000YYYY)
				addlw   0x30        ; Add offset for ASCII char set    (0030YYYY)
				call    data2LCD    ; Send byte in W to LCD
;
				swapf   BCD_1,w     ; Swap 1KHz BCD digit into lower nibble of W
				andlw   0x0F        ; Mask for lower nibble only       (0000XXXX)
				addlw   0x30        ; Add offset for ASCII char set    (0030XXXX)
				call    data2LCD    ; Send byte in W to LCD
;
																															
				movlw   ','         ; Get a period
				call    data2LCD    ; Send byte in W to LCD
				;movlw   0x88       ; Point to LCD digit number nine
				;call    cmnd2LCD   ; Send command byte in W to LCD

;
				movf    BCD_1,w     ; Put 100 Hz BCD digit into lower nibble of W
				andlw   0x0F        ; Mask for lower nibble only       (0000YYYY)
				addlw   0x30        ; Add offset for ASCII char set    (0030YYYY)
				call    data2LCD    ; Send data byte in W to LCD
;
				swapf   BCD_0,w     ; Swap 10 Hz BCD digit into lower nibble of W
				andlw   0x0F        ; Mask for lower nibble only       (0000XXXX)
				addlw   0x30        ; Add offset for ASCII char set    (0030XXXX)
				call    data2LCD    ; Send data byte in W to LCD
;
				movf    BCD_0,w     ; Put 1 Hz BCD digit into lower nibble of W
				andlw   0x0F        ; Mask for lower nibble only       (0000YYYY)
				addlw   0x30        ; Add offset for ASCII char set    (0030YYYY)
				call    data2LCD    ; Send byte in W to LCD
;

				movlw   ' '         ; Send a space
				call    data2LCD    ;   to position 12 of LCD
;
				movlw   'H'         ; Send a 'k'
				call    data2LCD    ;   to position 13 of LCD
;
				movlw   'z'         ; Send an "H"
				call    data2LCD    ;   to position 14 of LCD
;
				movlw   ' '         ; Send a 'z'
				call    data2LCD    ;   to position 15 of LCD
;
				movlw   ' '         ; Send a space
				call    data2LCD    ;   to position 16 of LCD
				
				movf	cur_pos,w
				call	cmnd2LCD
	return

; **************************************************************************************************
; *                                          busy_check                                            *
; **************************************************************************************************
; * This subroutine checks if the LCD is done with the last operation. It polls the LCD busy flag  *
; * to determine if any current operations are completed.                                          *
; **************************************************************************************************
; 
busy_check
		bcf	     LATB,0
		bcf	     LATB,1
		bcf	     LATB,2
		bcf	     LATB,3

		movlw     0x08                ; Set RB7 input, others outputs              
		movwf     TRISB               ;   via Tristate
		bcf       LATC,LCD_RS         ; Set up LCD for Read Busy Flag (RS = 0) 
		bsf       LATC,LCD_RW         ; Set up LCD for Read (RW = 1)  
		movlw     0xFF                ; Set up constant 255
		movwf     timer1              ;   for timer loop counter
LCD_is_busy
		bsf       LATC,LCD_E          ; Set E high
		movf      PORTB,w             ; Read PORTB into W
		movwf     LCD_read            ; Save W for later testing
		bcf       LATC,LCD_E          ; Drop E again
		nop                           ; Wait a
		nop                           ;   while
		bsf       LATC,LCD_E          ; Pulse E high (dummy read of lower nibble),
		nop                           ;   wait,
		bcf       LATC,LCD_E          ;   and drop E again
		decf      timer1,f            ; Decrement loop counter
		btfsc     STATUS,Z            ; Is loop counter down to zero?
		goto      not_busy            ; If yes, return regardless
          btfsc     LCD_read,3          ; Is Busy Flag (RB7) in save byte clear?       
       
		goto      LCD_is_busy         ; If not, it is busy so jump back
not_busy
	return                     


; **************************************************************************************************
; *                                          cmnd2LCD                                              *
; **************************************************************************************************           
; * This subroutine sends a Command or Data byte to the LCD                                        *
; **************************************************************************************************
;
cmnd2LCD   ; ****** Entry point ******
		movwf     LCD_char            ; Save byte to write to LCD
		clrf      rs_value            ; Remember to clear RS  (clear rs_value)   
		bcf       LATC,LCD_RS         ; Set RS for Command to LCD
	goto    write2LCD                  ; Go to common code
data2LCD   ; ****** Entry point ********
		movwf     LCD_char            ; Save byte to write to LCD
		bsf       rs_value,0          ; Remember to set RS (set bit 0 of rs_value)
		bsf       LATC,LCD_RS         ; Set RS for Data to LCD
write2LCD
	call    busy_check                 ; Check to see if LCD is ready for new data
		movlw	0x0F
		andwf	PORTB,f
		movlw     0x00                ; Set up to enable PORTB data pins
		movwf     TRISB               ; All pins (RB7..RB0) are back to outputs
		bcf       LATC,LCD_RW         ; Set LCD back to Write mode  (RW = 0)
		bcf       LATC,LCD_RS         ; Guess RS should be clear              
		btfsc     rs_value,0          ; Should RS be clear?  (is bit 0 == 0?) 
		bsf       LATC,LCD_RS         ; No, set RS                            
;
; Transfer Most Significant nibble  (XXXX portion of XXXXYYYY)
;
		movlw     0xF0                ; Set up mask                                        
		andwf     PORTB,f             ; Maintaing the info on the upper 4 pins, clear the lower
		swapf	LCD_char		     ; swap the nybbles of LCD_char
		movf      LCD_char,w          ; Put byte into W           
		andlw     0x0F                ; Mask to isolate the lower nybble in W (which is actually the upper due to the SWAPF)                 
		iorwf     PORTB,f             ; To RB7..RB4 with RB3..RB0 unchanged        
		bsf       LATC,LCD_E          ; Pulse the E line high,
		nop                           ;   wait, 
		bcf       LATC,LCD_E          ;   and drop it again
;
; Transfer Least Significant nibble  (YYYY portion of XXXXYYYY)
;
		movlw     0xF0                ; Set up mask                                        
		andwf     PORTB,f             ; Clear the lower 4 pins of Port B                        
		swapf     LCD_char,w          ; SwAp nybbles, returning the word to its original state                
		andlw     0x0F                ; Mask to isolate the lower nybble (which is actually the lower due to two swaps)                 
		iorwf     PORTB,f             ; Dump info to the lower 4 pins maintaining the info already on the upper 4 pins.        
		bsf       LATC,LCD_E          ; Pulse the E line high,
		nop                           ;   wait, 
		bcf       LATC,LCD_E          ;   and drop it again
	return

; **************************************************************************************************
; *                                             Delays                                             *
; **************************************************************************************************
; *  The delay routines are simply double-loop down-counters that are prefilled with various       *
; *  values.  The places where these values are prefilled are also subroutine entry points for the *
; *  various named delays.                                                                         *
; **************************************************************************************************

wait_a_sec  ; ****** Entry point ******    
        call    wait_256ms        ;       
        call    wait_256ms        ;       
wait_512ms  ; ****** Entry point ******
        call    wait_256ms        ;       
        call    wait_256ms        ;       
        return
wait_256ms  ; ****** Entry point ******    
        call    wait_128ms        ;
        call    wait_128ms        ;
        return
wait_128ms  ; ****** Entry point ******    
        movlw   0xFF              ; Set up outer loop 
        movwf   timer1            ;   counter to 255
        goto    outer_loop        ; Go to wait loops
wait_64ms  ; ****** Entry point ******     
        movlw   0x80              ; Set up outer loop
        movwf   timer1            ;   counter to 128
        goto    outer_loop        ; Go to wait loops
wait_32ms   ; ****** Entry point ******    
        movlw   0x40              ; Set up outer loop
        movwf   timer1            ;   counter to 64
        goto    outer_loop        ; Go to wait loops
wait_16ms   ; ****** Entry point ******    
        movlw   0x20              ; Set up outer loop
        movwf   timer1            ;   counter to 32  
        goto    outer_loop        ; Go to wait loops
wait_8ms   ; ****** Entry point ******     
        movlw   0x10              ; Set up outer loop
        movwf   timer1            ;   counter to 16
        goto	outer_loop        ; Into the wait loops
wait_1ms
        movlw	0x2
        movwf	timer1
;
; Wait loops used by other wait routines
;  - 1 microsecond per instruction (with a 4 MHz microprocessor crystal)
;  - 510 instructions per inner loop
;  - (Timer1 * 514) instructions (.514 msec) per outer loop
;  - Round off to .5 ms per outer loop
;
outer_loop                        
        movlw   0xFF              ; Set up inner loop counter
        movwf   timer2            ;   to 255
inner_loop
        decfsz  timer2,f          ; Decrement inner loop counter
        goto    inner_loop        ; If inner loop counter not down to zero, 
                                  ;   then go back to inner loop again
        decfsz  timer1,f          ; Yes, Decrement outer loop counter
        goto    outer_loop        ; If outer loop counter not down to zero,
                                  ;   then go back to outer loop again
        return                    ; Yes, return to caller

wait_ten_ops
          nop
          nop
          nop
          nop
          nop
          nop
          nop
          nop
          nop
	return
;       
; *****************************************************************************
;
        END
