
; ****************************************************************************
; * PICELgen - Signal Generator (VFO) with Direct Digital Synthesis          *
; *  Version 2.10                                                            *
; *  October 3, 2005                                                         *
; *                                                                          *
; ****************************************************************************
; Description:
; This is the control program for a DDS VFO built with an AD9850 DDS chip, a
; shaft encoder, three push button switches and a Hitatchi 44780-compatible
; Liquid crystal display.
;
;
; New Features for Version 2.1:
;
;       This is the same code base as Version 2.0 which has been adapted
;       for the newer PIC16F628A processor. With a few minor changes, it
;       will work with the PIC16F627 and PIC16F648A.
;
;
; New Features for Version 2.0:
;
;       1. Push Button Decade Selection. The LCD now displays an underline
;          cursor which identifies the lowest frequency digit (as well as
;          the higher decades) which will be modified as the shaft encoder
;          is turned.
;
;          PB1 moves the cursor to the left. PB2 moves the cursor to the right.
;          Holding either PB1 or PB2 (but not both) for more than 1 second
;          activates the auto repeat feature and the cursor advances automatically
;          until the button is released. Repeatability is enhanced and the user
;          controls how fast the frequency changes.
;
;          A build-time option allows the user to select the default decade
;          increment.
;
;       2. Support for the PCI-EL mechanical shaft encoder. A build-time
;          option permits the user to enable PIC-EL shaft encoder debounce
;          and per-detent frequency changes. This option may also be used
;          with optical shaft encoders but the frequency change rate will
;          be 1/4 the rate.
;
;       3. Programmable Band Memories. The user is now able to store his/her
;          favorite band frequencies in EEPROM. There are 14 Band memories
;          available and the concept of operating in a band is introduced.
;          By default, each band is programmed with a popular QRP CW
;          frequency or popular IF frequency. Of course, any frequency
;          may be stored in the band memory.
;
;       4. Programmable Startup Band. The user may program his/her favorite
;          startup band.
;
;       5. Dual Independent VFOs. The software supports two VFOs which are
;          band and frequency independent. VFO A may be copied to VFO B at
;          the user's option.
;
;       6. Support for AmQRP 8 and 16 character LCDs, plus aftermarket linearly
;          addressed displays that don't require a "long hop" to character
;          position 9.
;      
; Further details are presented in the PICgen2.0_release_notes.txt file.
;
; Features retained from Version 1.4:
;
;       1. BAND MEMORIES. A pushbutton switch (PIC-EL PB_2) allows the frequency
;          to be cycled around the HF ham bands. 
;
;       2. CALIBRATE MODE is entered if PB_1 is pressed during power-on. The
;          display is set to 10 MHz and remains fixed, even as adjustments are
;          being made. If pushbutton is held pressed, then turning the shaft
;          encoder will increase or decrease the value "osc" used to calculate
;          the DDS control word. The basic calibrate adjustment rate is very
;          low (on the order of a few cycles per turn of the encoder). A somewhat
;          faster adjustment speed is available by pressing the encoder shaft or
;          PB 3 down while turning.  
;
;          An external frequency counter on the DDS output is required to observe
;          this adjustment. Alternatively, tune a receiver to the 10 MHz WWV
;          broadcast and zero-beat the carrier. To exit calibrate mode, release
;          the pushbutton and turn the shaft encoder one more time. The calibrated
;          value of "osc" will then be stored in EEPROM memory.
;
;
; Features from Version 1.4 not retained:
;
;       1. VARIABLE RATE TUNING. This was removed in favor of selecting the
;          decade for updating.
;
;       2. MOVE UP/DOWN 1 MHz. The decade selection expands this option.
;
;       3. SAVE NEW START-UP FREQUENCY. Version 2.0 expands upon this option
;
;
;******************************************************************************
; Original Author - Curtis W. Preuss - WB2V
;
; Modification History
;    8/19/98 - Version 1 - Initial Version by Curtis W. Preuss - WB2V
;   12/xx/98 - Version 2 - Converted to MPASM by Bruce Stough, AA0ED 
;    4/21/99 - Version 3 - Fixed and modified by 
;                            Bruce Stough, AA0ED (sbs1@visi.com) and
;                            Craig Johnson, AA0ZZ (cbjohns@cbjohns.com)
;
;   10/31/03 PICELgen1.0 Modify for PIC Elmer project by Craig Johnson, AA0ZZ P
;                          1) Change to use 1x8 LCD instead of 1x16           P
;                              - Freq displayed as Hz (e.g. 14025000)         P
;                              - CAL freq displayed as Hz (e.g. 10000000)     P
;                          2) Set up to support the NJQRP DDS Daughterboard   P
;                              - 50 MHz oscillator                            P
;                          3) Change PORTB pin allocations to support PIC-EL  P
;                            - Change RB4-RB7 to RB0-RB3                      P
;                            - Change RB0 (DDS LOAD) to RB7                   P
;                            - Change RB1 (LCD_rs) to RB6                     P
;                            - Change RB2 (LCD_rw) to RB5                     P
;                            - Change RB3 (LCD_e) to RB4                      P
;                            - Change Busy_check routine to check correct bit P
;                            - Change cmnd2LCD/data2LCD routine - swap nibblesP
;                          4) Change RA2 to always be an LOW output           P
;                          5) Support pushbutton on RA3 instead of encoder    P
;                               shaft switch for bandswitch and calibrate     P
;                          6) Support pushbutton on RA4 for fast tuning       P
;                                                                             P
;    1/3/04    PICELgen1.1  Add Title and Version on start-up                 P
;    1/7/04    PICELgen1.2  Restructure main loop and change_band             P
;    2/2/04    PICELgen1.2a Fix confusing comment in the header re shaft sw.  P
;                           Fix comment in header regarding PB_2 for calib.   P
;    2/8/04    PICELgen1.3  Fix for reliable startup of DDS                   P
;                           Add code for 1 MHz steps (up or down)             P
;                           Add code to save new start-up frequency           P
;    3/12/04   PICELgen1.4  Remove use of watchdog timer  (temp sensitivity)  P
;                           Add code to support either 1x8 or 1x16 LCDs       P
;                            - Use #DEFINE to select the LCD type             P
;                           Fix calibrate routine so it stays in cal_loop     P
;
;
;    4/19/04   PICELgen2.0 Mods by Bob Okas, W3CD as follow:
;
;                           1. Added the features listed at the top.
;                           2. Fixed PORTA initialization bug that kept RA2
;                              as an input, which caused the speaker to
;                              continuously draw current.
;                           3. Added LCD command definitions.
;                           4. Put LCD messages in a table to reduce program
;                              memory usage. Added message display function.
;                           5. Modified sub_step to re-complement fstep_3..0
;                           6. Rewrote pushbutton handler.
;                           7. Added feature to display a message when
;                              EEPROM contents are changed.
;                           8. Changed default startup frequency to 7040 KHz.
;                           9. Removed commented-out legacy code.
;                          10. Changed hard-coded constants in program code
;                              to named constants which appear at the top
;                              of the program.
;                          11. Added support for linearly addressed 16x1 LCDs
;                          12. Removed the "#" characters preceeding IF, ELSE
;                              and ENDIF directives to support other assemblers
;                              besides MPASM.
;                          13. Significant re-writes to optimize program memory
;                              usage which are too numerous to mention here.
;
;    4/19/04   PICELgen2.01:
;
;                           1. Fixed AmQRP 16x1 display and cursor addressing
;                              bugs. Rearranged some initialization code. W3CD
;
;    10/3/05   PICELgen2.10:
;                           Adapted for PIC16F628A. W3CD
;
;    25/4/12   PICELgen2.10b:
;		    Modified to include AD9851 VK5TM
;
;    29/4/12   PICELgen2.10d:
;                           Removed LED Code. Added 'A' & 'B' to VFO frequency
;                           display.
;
;*****************************************************************************
;                                                                             P
; Target Controller -      PIC16F84A in PIC-EL board                          P 
;                          __________                                         P
;     PB_3-Speaker----RA2 |1       18| RA1---------ENCODER A                  P
;     PB_2-Bandswitch-RA3 |2       17| RA0---------ENCODER B                  P
;     PB_1-Tuning etc-RA4 |3       16| OSC1--------NC                         P
;     +5V-----------!MCLR |4       15| OSC2--------NC                         P
;     Ground----------Vss |5       14| VDD---------+5 V                       P
;     LCD11-----------RB0 |6       13| RB7---------DDS_LOAD                   P
;     LCD12-----------RB1 |7       12| RB6---------LCD_rs  (LCD Pin 4)        P
;     LCD13/DDS_CLK---RB2 |8       11| RB5---------LCD_rw  (LCD Pin 5)        P  
;     LCD14/DDS_DATA--RB3 |9       10| RB4---------LCD_e   (LCD Pin 6)        P
;                          ----------                                         P
;                                                                             P
; ****************************************************************************
; *    Device type and options.                                              *
; ****************************************************************************
;
        processor       PIC16F628A
        radix           dec

		#include P16F628A.inc    ; Include register and memory definitions

        errorlevel 0, -205, -220, -302  ; Skip nuisance messages 


;
; ****************************************************************************
; * Configuration fuse information:                                          *
; ****************************************************************************

        __config  _CP_OFF & _LVP_OFF & _BODEN_OFF & _PWRTE_ON & _WDT_OFF & _INTOSC_OSC_NOCLKOUT

;*****************************************************************************
;
;		PIC-EL Hardware Configurations
;
;*****************************************************************************


;
;   LCD Selections. The first batch of AmQRP PIC-EL boards were shipped with
;   8 character x 1 line LCDs. Later shipments used 16x1 LCDs which are
;   addressed as two 8x1 blocks. To get to the 9th character, the cursor
;   must be explicitly moved. There are other LCDs which employ linear display
;   addressing and no special actions are required to move from the 8th to the
;   9th character. These displays have been dubbed "linear" for the purposes of
;   this program. Select the appropriate #define to handle the appropriate LCD
;

;
;   Select only one of the following 3 #defines for your LCD type
;

;#define LCD_8          ; Original 8 character AmQRP LCD
;#define LCD_16         ; Later AmQRP 16 character LCD
;!!! #define LCD_16L         ; Generic 16 character LCD with linear addressing


;
;   The following definitions depend upon the selection above.
;

;!!! IFDEF LCD_8            ; AmQRP 8x1 LCDs -- Shipped with earlier kits
;!!! #define LCDCHAR 8       ; Enable 8-character LCD program features
;!!! ENDIF

;!!! IFDEF LCD_16           ; AmQRP 16x1 LCDs -- Shipped with later kits
;!!! #define LCDCHAR 16      ; Enable 16-character LCD program features
;!!! #define LCD16_LINEAR 0  ; Use for AmQRP 16x1 displays
;!!! ENDIF

;!!! IFDEF LCD_16L          ; Alternative, linearly addressed 16x1 displays
;!!! #define LCDCHAR 16      ; Enable 16-character LCD program features
;!!! #define LCD16_LINEAR 1  ; Use linear addressing
;!!! ENDIF



; Defining DETENT_ENCODER enables software to debounce the PIC-EL mechanical
; encoder and modifies the incrementing so that only one count changes at
; each detent. Comment out the line below for optical or detent-less encoders

;!!! #define DETENT_ENCODER

; Dual VFOs. Enabling this option exacts a price in the PIC-EL board
; in that it causes RA2 to become an input. This causes Q5 to turn on and
; the speaker draws current. Both devices get warm. If this option is
; desired, then it is a good idea to lift one leg of R20 to keep the speaker
; and Q5 cool.

;!!! #define DUAL_VFO

;
; Keep the Version number here
;

#define CODE_VERSION "2.10"


;
; *****************************************************************************
; * DDS Frequency control equates.  These may be changed to accommodate the   *
; * reference clock frequency and the desired upper frequency limit 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
;
; 180.00 MHz     0x17         0xDC         0x65         0xDF
; 125.00 MHz     0x22         0x5C         0x17         0xCA
; 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 100 MHz Oscillator =======

;ref_osc_3           equ 0x2A      ; Most significant osc byte
;ref_osc_2           equ 0xF3      ; Next byte
;ref_osc_1           equ 0x1D      ; Next byte
;ref_osc_0           equ 0xC4      ; Least significant byte

;==== Currently set for 125 MHz Oscillator ======= Modified 22/2/12 TM

;ref_osc_3           equ 0x22      ; Most significant osc byte
;ref_osc_2           equ 0x5C      ; Next byte
;ref_osc_1           equ 0x17      ; Next byte
;ref_osc_0           equ 0xD0     ; Least significant byte

;==== Currently set for 180 MHz Oscillator ======= For AD9851 Modified 22/2/12 TM

ref_osc_3           equ 0x17      ; Most significant osc byte
ref_osc_2           equ 0xDC      ; Next byte
ref_osc_1           equ 0x65      ; Next byte
ref_osc_0           equ 0xDF     ; Least significant byte

; limit3..0 specify the upper limit frequency (in Hz) expressed as a 32 bit
; integer. This should not be set to more than one third of the reference
; oscillator frequency.  The output filter of the DDS board must be designed
; to pass frequencies up to the maximum.

;limit_3             equ 0x01      ; Most significant byte for 30 MHz
;limit_2             equ 0xC9      ; Next byte
;limit_1             equ 0xC3      ; Next byte
;limit_0             equ 0x80      ; Least significant byte

;--------------------------------------------------------------------------------------------------------
; Modified 22/2/12 TM - 40MHz upper Frequency limit (AD9850)
; limit3..0 specify the upper limit frequency (in Hz) expressed as a 32 bit
; integer. This should not be set to more than one third of the reference
; oscillator frequency.  The output filter of the DDS board must be designed
; to pass frequencies up to the maximum.

;limit_3             equ 0x02      ; Most significant byte for 40 MHz
;limit_2             equ 0x62      ; Next byte
;limit_1             equ 0x5A      ; Next byte
;limit_0             equ 0x00      ; Least significant byte
;---------------------------------------------------------------------------------------------------------

;--------------------------------------------------------------------------------------------------------
; Modified 22/2/12 TM - 60MHz upper Frequency limit (AD9851)
; limit3..0 specify the upper limit frequency (in Hz) expressed as a 32 bit
; integer. This should not be set to more than one third of the reference
; oscillator frequency.  The output filter of the DDS board must be designed
; to pass frequencies up to the maximum.

limit_3             equ 0x03      ; Most significant byte for 60 MHz
limit_2             equ 0x93      ; Next byte
limit_1             equ 0x87      ; Next byte
limit_0             equ 0x00      ; Least significant byte
;---------------------------------------------------------------------------------------------------------

; The following is the definition of the 10.00000 MHz
; calibration frequency

cal_freq_0          equ 0x80      ; LSB
cal_freq_1          equ 0x96
cal_freq_2          equ 0x98
cal_freq_3          equ 0x00      ; MSB


; The following defines the default decade increment at power up. Chose
; any value between 0 and 7 to customize your version.

default_decade      equ  0 ;############

;*************************************************************************************
;
;                       General definitions
;
;*************************************************************************************

EEPROM_BASE         equ 0x2100     ; Where EEPROM begins in the 16F84A

RAM_BASE            equ 0x20       ; Where General Purpose Regs. begin in 16F628A

RAM_ALL             equ 0x70       ; Origin in BANK 0 of ram accessable across all pages

TABLE_ADJUST        equ 3          ; Value to adjust table pointers

MSB                 equ 7          ; Most significant bit in a byte

UP_BIT              equ 0x02       ; Used by poll_encoder to determine direction

DIR_BIT             equ 1          ; Bit to test for shaft encoder direction

FLAG_MASK           equ 0xff       ; Mask used to set status flags.

BAND_ADJUST         equ -1         ; Added to vfo_pointer contents to get vfo_X_band #########

LOW_NYBBLE_MASK     equ 0x0f       ; Mask used to extract low 4 bits in a byte

HI_NYBBLE_MASK      equ 0xf0       ; Mask used to extract high 4 bits in a byte

ASCII_NUM_BASE      equ 0x30       ; Added to BCD digits to get printable digits

ENCODER_BITS        equ 0x03       ; Mask applied to PORTA to extract encoder position

TEN                 equ 10         ;

VFO_SEL             equ 0          ; VFO A/B select bit

FREQ_COUNT          equ  4         ; Number of frequency bytes to copy

DECADE_MASK         equ  0x07      ; Applied to decade adjustments

SMALL_FSTEP         equ  0x05      ; Small cal. freq. step. About .4 Hz

LARGE_FSTEP         equ  0x80      ; Large cal. freq. step. About 3 Hz

;###################################################################
;!!! IFDEF DUAL_VFO

PORTA_CONTROL       equ   0xff     ; Port A is all inputs for Dual VFOs

;!!! ELSE

;!!! PORTA_CONTROL       equ   0xfb     ; Port A is all inputs except RA2

;!!! ENDIF
;####################################################################


;**************************************************************************************
;                 PB_flags bits and related definitions
;
;	These bits define the operation of PB_1 and PB_2 during startup and normal
;	operations.
;
;**************************************************************************************

PB_RPT_FLG          equ 0           ; Indicates that PB was repeating
PCAL_FLG            equ 1           ; Indicates that cal mode is enabled####################
PB_1_DLY            equ 2           ; Indicates that PB_1 repeat delay countdown is active###########
PB_2_DLY            equ 3           ; Indicates that PB_2 repeat delay countdown is active###########
BAND_MODE           equ 4           ; Indicates that Encoder was turned while PB_2 is pressed#######
VFO_ACTION          equ 5           ; Indicates that PB3 was pressed and that a vfo action
                                    ; was requested.########################

DECADE_DECREMENT    equ 0xff        ; Value which decrements cursor frequency decade ]
DECADE_INCREMENT    equ 0x01        ; Value which increments cursor frequency decade ]CURSOR POSITION????

PB_RPT_WAIT         equ 32          ; 1 second delay before autorepeat
PB_RPT_DLY          equ  4          ; 1/8 second between repeats
PB_INIT_FREQ_WAIT   equ 64          ; 2 second delay before new start freq set



;**************************************************************************************
;
;                 PIC-EL LED Control bits and related definitions
;
;**************************************************************************************

;!!!LED1_ON             equ  0x07       ; Mask AND'd with PORTB to turn on LED1
;!!!LED2_ON             equ  0x0b       ; Mask AND'd with PORTB to turn on LED2
;!!!LED3_ON             equ  0x0d       ; Mask AND'd with PORTB to turn on LED3

;!!!LED1_OFF            equ  0x08       ; Value OR'd with PORTB to turn off LED1
;!!!LED2_OFF            equ  0x04       ; Value OR'd with PORTB to turn off LED2
;!!!LED3_OFF            equ  0x02       ; Value OR'd with PORTB to turn off LED3

;!!!LEDS_OFF            equ  0x0e       ; Port B bits to turn off all LEDs
;!!!LED_MASK            equ  0xf1       ; Port B mask to isolate led control bits


;*****************************************************************************
;
; 			LCD Command Definitions
;
; The following are a series of command definitions and parameters that the
; Hitatchi 44780 LCD controller chip recognizes. Some commands have parameters
; which may be or'ed into the basic data to form a single-byte command. In
; such cases, these parameters have been grouped directly underneath the basic
; command data and bear descriptive names and comments related to their
; functions.
;
;*****************************************************************************

CMD_CLEAR_LCD       equ  0x01	; Clears the Display

CMD_RESET_LCD       equ  0x03   ; Resets the display. Write this 3 times

CMD_SET_IFC_1       equ  0x02   ; Interface programming word 1
IFC1_4_BIT          equ  0x00   ; 4 bit I/O with LCD
;!!!IFC1_8_BIT          equ  0x01   ; 8-bit I/O (not used with PIC-EL)

PICEL_IFC1          equ CMD_SET_IFC_1 + IFC1_4_BIT

CMD_SET_IFC_2       equ  0x20   ; Interface programming word 2
IFC2_4_BIT          equ  0x00   ; 4-bit I/O
;!!!IFC2_8_BIT          equ  0x10   ; 8-bit I/O (not used with PIC-EL)
IFC2_1_LINES        equ  0x00   ; Set display to 1 line of characters
;!!!IFC2_2_LINES        equ  0x08   ; Set display to 2 lines of characters
IFC2_FONT_5_7       equ  0x00   ; Sets display font to 5x7 dot character matrix
;!!!IFC2_FONT_5_10      equ  0x04   ; Sets display font to 5x10 dot character matrix

;!!! IF LCDCHAR == 16
;!!! IF LCD16_LINEAR == 0
;!!! PICEL_IFC2          equ  CMD_SET_IFC_2 + IFC2_4_BIT + IFC2_2_LINES + IFC2_FONT_5_7
;!!! ELSE
;!!! PICEL_IFC2          equ  CMD_SET_IFC_2 + IFC2_4_BIT + IFC2_1_LINES + IFC2_FONT_5_7
;!!! ENDIF
;!!! ELSE
PICEL_IFC2          equ  CMD_SET_IFC_2 + IFC2_4_BIT + IFC2_1_LINES + IFC2_FONT_5_7
;!!! ENDIF

CMD_DISP_CUR_EN     equ  0x08   ; Base command for display & cursor control
DISP_OFF            equ  0x00   ; Disables display
DISP_ON             equ  0x04   ; Enables display
CUR_OFF             equ  0x00   ; Disables cursor
CUR_ON              equ  0x02   ; Enables underline cursor
BLINK_OFF           equ  0x00   ; Disables blinking block cursor
;!!!BLINK_ON            equ  0x01   ; Enables blinking block cursor

PICEL_DISPLAY_OFF   equ  CMD_DISP_CUR_EN + DISP_OFF + CUR_OFF + BLINK_OFF
PICEL_DISPLAY_ON    equ  CMD_DISP_CUR_EN + DISP_ON + CUR_ON

CMD_HOME_CUR_LCD    equ  0x02	; Return Display and cursor to home pos.

CMD_SET_INC         equ  0x04	; Sets Display and cursor movement
;!!!INC_DISPLAY_POS     equ  0x01	; Shifts the display after data write
INC_CURSOR_POS      equ  0x02	; Increments the cursor along with display

PICEL_CURSOR_RT     equ  CMD_SET_INC + INC_CURSOR_POS

;!!!CMD_SET_DSP_CUR     equ  0x08	; Controls Display and cursor
;!!!ENABLE_BLOCK_BLINK  equ  0x01	; Blinking Block if enabled
;!!!ENABLE_CURSOR       equ  0x02	; Underline if not blinking block
;!!!ENABLE_DISPLAY      equ  0x04	; Turns on Display

;!!!CMD_SET_SCROLL      equ  0x10	; Controls Display/Cursor Scrolling
;!!!SCROLL_ENABLE       equ  0x08	; Enables Display/Cursor Scrolling
;!!!SCROLL_RIGHT        equ  0x04	; Enables Right scroll. Otherwise it's Left.

;!!!CMD_CURSOR_CGRAM    equ  0x40	; Move cursor position within character
                                ; generator memory. "OR" in the CGRAM
                                ; address to this command. Valid range
                                ; is 0x00 - 0x3f

CMD_MOV_CUR_DISP    equ  0x80	; Move cursor position within Display
                                ; "OR" in the Display position to this command.
                                ; 0x00 is the leftmost position.
;!!!OFFSET_9            equ  0x40   ; Offset added to above command to position
                                ; cursor at 9th character. Used for AmQRP
                                ; 16 character LCDs.

;!!!PICEL_LONG_9        equ  CMD_MOV_CUR_DISP + OFFSET_9

;!!!LCD_POS_9           equ  8      ; Cursor position 9 on 16 character LCDs

;
; ****************************************************************************
; * Assign names to IO pins.                                                 *
; ****************************************************************************

;
;   B register bits:
;
		
DDS_clk   equ   0x02              ; AD9850 write clock
DDS_dat   equ   0x03              ; AD9850 serial data input
LCD_busy  equ   0x03              ; LCD busy bit 
LCD_e     equ   0x04              ; 0=disable, 1=enable
LCD_rw    equ   0x05              ; 0=write, 1=read
LCD_rs    equ   0x06              ; 0=instruction, 1=data
DDS_load  equ   0x07              ; Update pin on AD9850

; 
;   A register bits:
;

;!!!SPEAKER   equ   0x02              ; Speaker (always exit with a low output)   P
PB_3      equ   0x02              ; PB also on this pin                       P
PB_2      equ   0x03              ; Bandswitch Pushbutton,                    P
PB_1      equ   0x04              ; Tuning-increment/Calibrate Pushbutton     P


;
; ****************************************************************************
; * ID location information:                                                 *
; * (MPASM warns about DW here, don't worry)                                 *
; ****************************************************************************
;
        ORG     0x2000
        DATA    0x57
        DATA    0x33
        DATA    0x43
        DATA    0x44
;
;
; ****************************************************************************
; * Setup the initial constant, based on the frequency of the reference      *
; * oscillator.  This can be tweaked with the calibrate function.            *
; ****************************************************************************
; 
        ORG     EEPROM_BASE

; ref_osc bytes appear as the first 4 bytes of EEPROM

        DATA    ref_osc_0
        DATA    ref_osc_1
        DATA    ref_osc_2
        DATA    ref_osc_3

startup_loc  ;#################
startup_band   equ startup_loc - EEPROM_BASE  ;#####################

        DATA    0x02        ; Band with which the PIC-EL is booted  ;###################

;###########################################################################
;*****************************************************************************
;
;       Programmable Band Memories. The following EEPROM locations store
;       the user-programmed frequencies of their choice. The notion of
;       bands is a loose one since any frequency can be stored here. The
;       program defaults are the U.S. QRP CW frequencies for each band.
;		Data is expressed in Hz and is stored LSB first.
;
;*****************************************************************************

band_loc

band_table equ band_loc - EEPROM_BASE

        DATA    0x50, 0x9e, 0x1b, 0x00    ; Band  0   1.810 MHz
        DATA    0x40, 0x52, 0x36, 0x00    ; Band  1   3.560 MHz
        DATA    0x00, 0x6c, 0x6b, 0x00    ; Band  2   7.040 MHz
        DATA    0xa0, 0x5b, 0x9a, 0x00    ; Band  3  10.116 MHz
        DATA    0xe0, 0x89, 0xd6, 0x00    ; Band  4  14.060 MHz
        DATA    0x80, 0x1f, 0x14, 0x01    ; Band  5  18.096 MHz
        DATA    0xa0, 0x59, 0x41, 0x01    ; Band  6  21.060 MHz
        DATA    0x10, 0x09, 0x7c, 0x01    ; Band  7  24.906 MHz
        DATA    0x60, 0x29, 0xac, 0x01    ; Band  8  28.060 MHz

;
;    Some popular IF frequencies
;
        DATA    0xe0, 0x44, 0xa3, 0x00    ; Band  9 10.700 MHz - FM receivers
        DATA    0xc8, 0x22, 0x56, 0x00    ; Band 10  5.645 MHz - Drake 4 series
        DATA    0x38, 0xff, 0x4a, 0x00    ; Band 11  4.915 MHz - QRP rigs
        DATA    0xb8, 0xcd, 0x33, 0x00    ; Band 12  3.395 MHz - Heathkit SB series
        DATA    0x58, 0xf1, 0x06, 0x00    ; Band 13    455 KHz - Lots!!!

band_end_loc
num_bands   equ (band_end_loc - band_loc) / 4

        DATA    0x00, 0x00, 0x00          ; Clear the last 3 bytes
;###############################################################################

;
; ****************************************************************************
; *           Allocate variables in general purpose register space           *
; ****************************************************************************
;
        CBLOCK  RAM_BASE          ; Start Data Block

        freq_0                    ; Display frequency (hex) 
        freq_1                    ;  (4 bytes) 
        freq_2                    ; Used by bin2BCD only
        freq_3         

        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_0                     ; Current oscillator 
        osc_1                     ;  (4 bytes)
        osc_2
        osc_3

        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                    ;   "

        ren_new                   ; New value of encoder pins A and B
        ren_old                   ; Old value of encoder pins A and B
        ren_read                  ; Encoder pins A and B and switches
        last_dir                  ; Indicates last direction of encoder
        next_dir                  ; Indicates expected direction

        count                     ; Multipurpose loop counter

        rs_value                  ; The LCD rs line flag value

        PB_flags                  ; Pushbutton flags
        PB_wait_count             ; PB repeat control timer

        temp                      ; Miscellaneous variable

        decade                    ; Frequency decade subject to updating
        cur_pos                   ; Cursor position: 0-7 for 8 char LCD

        message_pointer           ; Temp message pointer variable

        enc_counter               ; Divide by 4 counter for mechanical encoder

        vfo_select                ; A = 0x00, B = 0x01

                                  ; N.B. Don't disturb the order of the VFO
                                  ; variables as their adjacency is required.
        vfo_a_band                ; Stores the current VFO A band
        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

        vfo_b_band                ; Current VFO B band storage
        vfo_b_0                   ; Used to store the frequency setting
        vfo_b_1                   ; of VFO B.
        vfo_b_2
        vfo_b_3

        vfo_pointer               ; pointer to current vfo_X_0.

        ;!!!led_states                ; Used to store which leds are on.

        ENDC                      ; End of Data Block



        CBLOCK  RAM_ALL           ; Common RAM base

        ee_addr                   ; Storage for eeprom address

        ENDC

; 
; ****************************************************************************
; * The 16F84 resets to 0x00.                                                * 
; * The Interrupt vector is at 0x04. (Unused)                                *
; ****************************************************************************             
;

        ORG     0x0000                
reset_entry
        goto    start             ; Jump around the band table to main program


;****************************************************************************  
;
;                        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 get be assigned to
; fstep_3, fstep_2, fstep_1 and fstep_0. Data is stored MSB first.
;
;****************************************************************************

decade_table
        addwf   PCL,f   ; Jump to the appropriate value to return

        dt      0x00, 0x00, 0x00, 0x01  ; 1Hz increment
        dt      0x00, 0x00, 0x00, 0x0a  ; 10 Hz increment
        dt      0x00, 0x00, 0x00, 0x64  ; 100 Hz increment
        dt      0x00, 0x00, 0x03, 0xe8  ; 1 KHz increment
        dt      0x00, 0x00, 0x27, 0x10  ; 10 KHz increment
        dt      0x00, 0x01, 0x86, 0xa0  ; 100 KHz increment
        dt      0x00, 0x0f, 0x42, 0x40  ; 1 MHz increment
        dt      0x00, 0x98, 0x96, 0x80  ; 10 MHz increment


;****************************************************************************  
;
;                        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
        addwf   PCL,f   ; Jump to the appropriate value to return

;!!! IF LCDCHAR == 8
        dt      7, 6, 5, 4, 3, 2, 1, 0
;!!! ELSE
;!!!        dt      10, 9, 8, 6, 5, 4, 2, 1 ;INCREMENT? OR DECREMENT? TO FIX CURSOR POSITION???
;!!!        dt      11, 10, 9, 7, 6, 5, 3, 2 ;????
;!!! ENDIF

;****************************************************************************  
;
; Message Table. All of the PIC-EL LCD messages are stored here. The last
; byte of the message string is set to 0 to indicate the end of string. The
; messages are indexed via an offset value, which is calculated at assembly
; time.
;
;****************************************************************************

message_table
        addwf   PCL,f             ; Calculate the return value address
                                  ; and jump to the appropriate character
messages

sign_on_msg                       ; Displayed at power-on
        dt     "SV3ORA", 0
sign_msg_offset	equ	sign_on_msg - messages


;ver_msg                           ; Displayed at power-on
;
;;!!! IF LCDCHAR == 8
;;!!!        dt     "Ver ", CODE_VERSION, 0
;;!!! ELSE
;        dt     " Ver", CODE_VERSION, 0
;;!!! ENDIF
;ver_msg_offset  equ    ver_msg - messages


band_msg                          ; Displayed during band changes and
        dt      "Band ", 0        ; Startup band writes
band_msg_offset equ    band_msg - messages


update_msg                        ; Displayed during EEPROM writes
        dt      "Update", 0
upd_msg_offset  equ    update_msg - messages

init_msg                          ; Displayed during startup band writes
        dt      "InitBand", 0
init_msg_offset equ   init_msg - messages


;!!! IF LCDCHAR == 16

;khz_msg
;        dt      " KHz",0          ; KHz display for 16 char lcd only
;khz_offset      equ    khz_msg - messages
;
;calibrate_msg                     ; Displayed during CAL mode only
;        dt      "CAL ", 0         ; and only on 16 character LCDs
;cal_offset      equ    calibrate_msg - messages

;!!! ENDIF



;
; *****************************************************************************
; *                                                                           *
; * 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

        bsf     STATUS,RP0        ; Switch to bank 1
        clrf    TXSTA             ; Disable USART Transmitter
        clrf    PIE1              ; Clear all interrupt enable bits
        clrf    VRCON             ; Disable voltage reference

        bcf   OPTION_REG,NOT_RBPU ; Enable weak pullups
        movlw   PORTA_CONTROL     ; Set Port A bits.
        movwf   TRISA             ;
        clrf    TRISB             ; Set port B to all outputs
        bcf     STATUS,RP0        ; Switch back to bank 0

; Initialize DDS Module with zero freq - added 01/04/2013

	clrf	AD9850_0	; AD9850/51 control word 
	clrf	AD9850_1	; (5 bytes)
	clrf	AD9850_2
	clrf	AD9850_3
	clrf	AD9850_4
	call	send_dds_word1	; Send it to the DDS
	call	send_dds_word1	; twice to be sure

;!!! IFNDEF DUAL_VFO
                                  ; Move here to really clear spkr bit - V2.0
;!!!        bcf     PORTA,SPEAKER     ; Set speaker output low (don't use here)
;!!! ENDIF 

        call    init_LCD          ; Initialize the LCD
;!!!        movlw   LEDS_OFF          ; Turn off LEDS at the start
;!!!        movwf   led_states
        call    display_version   ; Display title and version number

;
; Read reference oscillator values from EEPROM
;
        movlw   osc_0             ; Get osc_0 address
        movwf   FSR               ; save for indirect addressing
        clrf    ee_addr           ; Osc values are at EEPROM start
        call    read_EEPROM_freq  ; Read the EEPROM and save in osc bytes

;

;
; Initialize other variables.
;
        clrf    last_dir          ; Clear the knob direction indicator
        clrf    PB_flags          ; Clear the flags

;
; Get the power on encoder value.
;
        movf    PORTA,w	          ; Read port A
        movwf   ren_read          ; Save it in ren_read
        movlw   ENCODER_BITS      ; Get encoder mask (RA0 and RA1)  
        andwf   ren_read,w        ; Get encoder bits
        movwf   ren_old	          ; Save in ren_old

;
;  Enter Calibrate Mode if push button is pressed while turning the 
;  power on.
;

        btfss   ren_read,PB_1     ; Tuning-increment/Cal pushbutton pressed?
        call    calibrate         ; Yes, calibrate
                                  ; Else, continue with rest of initialization

; Select VFO A at startup and establish pointer

        clrf    vfo_select        ; Start up with VFO A            
        movlw   vfo_a_0           ; Get address of VFO A registers 
        movwf   vfo_pointer       ; save in pointer register       

; Initialize other variables.

        movlw   default_decade    ; Get the default decade increment 
        movwf   decade            ; Store in decade                  
        clrw                      ; Prepare W for update             
        call    update_decade     ; Init fstep3..0 and cur_pos       


; Now get the user-programmed (or default) startup band and the
; the programmed startup frequency.
;

        movlw   startup_band  ; Get EEPROM startup band offset
        movwf   ee_addr           ;
        call    read_EEPROM       ; Get the startup band data into W
        movwf   vfo_a_band        ; Establish operating band for vfo a
        call    get_band          ; Establish the operating frequency
;!!! IFDEF DUAL_VFO
        call    copy_a_to_b       ; Copy current freq and band to vfo_b
;!!! ENDIF
        call    clear_lcd         ; Clear the display
        movlw   band_msg_offset   ; Get the band message offset into W
        call    display_message   ; Send message to LCD.
        call    show_band         ; Display the band number
        call    wait_512ms        ; Display band number for 1/2 second

;!!!        movlw   LED1_ON           ; Turn on LED1 for VFO A active
;!!!        movwf   led_states

;
; Display the power-on frequency.
;
        call    show_freq         ; COnvert to BCD and display it
;
; Send power on-frequency to the DDS chip.
;
        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 

;
; Fall into the Main Program Loop
;

; *****************************************************************************
; *                                                                           *
; * Purpose:  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 (PIC-EL PB_1) is not pressed,P
; *           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.    *
; *                                                                           *
; *           If the bandswitch pushbutton (PIC-EL PB_2) is pressed while     P
; *           turning the encoder then the freq is loaded with a constant     *
; *           stored in band_table.  The variable band is used as an index    *
; *           into the table.  Band is incremented or decremented based on    *
; *           the encoder direction.                                          *
; *                                                                           *
; *   Input:  None.                                                           *
; *                                                                           *
; *  Output:  None.                                                           *
; *                                                                           *
; *****************************************************************************
;

main
        call    poll_encoder      ; Check for knob movement (wait there!)
                                  ; Return here when encoder change detected
        btfss  PB_flags,BAND_MODE ; Is band change mode active?
        goto    step              ; No, just step    
        call    change_band       ; Yes, change_band
        goto    main

 
;******************************************************************************
;
;    Function: step 
;
;    Purpose: 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.
;
;    Rev 2.0: poll_encoder now determines the step size and stores the
;             appropriate frequency increment in fstep_3..0. In this version,
;             the accelerated adjustment to step size has been eliminated in
;             favor of trying the PB_1 method of decade selection. 3-26-04 W3CD
;
;*******************************************************************************

step                              ; Based on the knob direction, either add or
                                  ; subtract the increment, then update the
                                  ; LCD and DDS.
        btfsc   last_dir,DIR_BIT  ; 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
        call    check_add         ; Make sure we did not exceed the maximum

update
        call    show_freq         ; Display the new frequency on the LCD
        call    send_dds_word     ; Send the new control word to the DDS chip

        goto    main              ; Continue main loop



; *****************************************************************************
;
;    Function: change_band
;
;    Purpose:  This routine increments through the band table each time the
;              knob moves a step, updating the LCD and DDS, until the band
;              button is no longer pushed.
;
;    Input:    The value of the band push button and the encoder bits
;
;    Output:   Updated freq value, and new frequency on the LCD and DDS.
;
;    Revisions:
;              Rev 2.0: Modified for indexing into vfo_a_band and vfo_b_band 
;                       registers. 4-7-04 W3CD
;
; *****************************************************************************

change_band

        movlw   BAND_ADJUST       ; Get offset to vfo_X_band
        addwf   vfo_pointer,w     ; Add to vfo_X_0 to point to vfo_X_band
                                  ;
        movwf   FSR               ; Store in file select register
        btfsc   last_dir,DIR_BIT  ; Are we going up in the band list?
        goto    band_up           ; If so, increment the band number
                                  ; Else, we are going down in the band list
        decf    INDF,f            ; Decrement the band number pointed to by FSR
        btfss   INDF,MSB          ; If bit 7 is set, then band number is negative
        goto    band_ok           ; If still positive, then band number is OK
                                  ; Else wrap around to top
        movlw   num_bands - 1     ; Get the top band number
        movwf   INDF              ; Save it in band pointed to by FSR
        goto    band_ok           ; Finish up

band_up                           ; Arrive here when incrementing band number
        incf    INDF,f            ; Do it
        movlw   num_bands         ; Load w with max band number + 1
        subwf   INDF,w            ; Test if over the limit
        btfss   STATUS,C          ; If carry is set, then we're over
        goto    band_ok           ; Else, the band number is within range
        clrf    INDF              ; If over, then reset the band number

band_ok                           ; Arrive here with a valid band number
        call    clear_lcd         ; Clear the display
        movlw   band_msg_offset   ; Get the band message offset into W
        call    display_message   ; Send message to LCD.
        call    show_band         ; Display the band number
        call    wait_512ms        ; Display band number for 1/4 second - 29/4/12 changed to 1/2sec
                                  ; Now display the frequency
        call    get_band          ; Read the band frequency from EEPROM
        call    show_freq         ; Display the frequency on the LCD
        call    send_dds_word     ; Send the control word to the DDS chip
        return                    ; Back to the caller


;*****************************************************************************
;
;    Function: get_band
;
;    Purpose:  This routine reads the frequency value of a band table entry
;              from EEPROM and stores it in the active VFO registers
; 
;    Input:    vfo_pointer
;
;    Output:   The band frequency in the current vfo frequency registers
;
;    Revisions:
;              Rev 2.0: The frequency is now read out of EEPROM and stored in
;                      either the VFO A or VFO B frequency registers. - W3CD
;
;*****************************************************************************


get_band
        movf    vfo_pointer,w     ; Get pointer to vfo_X_0
        addlw   BAND_ADJUST       ; Subtract offset to get vfo_X_band
        movwf   FSR               ; store it in file select register

        movlw   band_table        ; Get starting offset of EEPROM band table
        addwf   INDF,w            ; Add the band number * 4
        addwf   INDF,w            ;   to get the
        addwf   INDF,w            ;     proper offset
        addwf   INDF,w            ;       into the band table
        movwf   ee_addr             ; Prepare EEPROM address
                                  ;
        incf    FSR,f             ; Now point to vfo_X_0
        call    read_EEPROM_freq  ; Get the frequency
        return                    ; Done!


; *****************************************************************************
;
;    Function: add_step
;
;    Purpose:  This routine adds the 32 bit value of fstep to the 32 bit
;              value in freq.  When incrementing, the fstep value is a
;              positive integer.  When decrementing, fstep is the complement
;              of the value being subtracted.
;
;     Input:   The 32 bit values in fstep and freq
;
;     Output:  The sum of fstep and freq is stored in freq.  When
;              incrementing this value may exceed the maximum.  When
;              decrementing, it may go negative.
;
;     Revisions:
;              Rev 2.0 Modified for use with dual VFOs. 4-7-04 W3CD
;
; *****************************************************************************

add_step
        clrf    temp              ; Clear the temp variable
                                  ;
        movf    vfo_pointer,w     ; Get the pointer to vfo_X_0
        movwf   FSR               ; Put this into FSR for indirect addressing

        movf    fstep_0,w         ; Get low byte of the increment
        addwf   INDF,f            ; Add it to the low byte of freq
                                  ; There may be a carry out of this add...
        movlw   3                 ; Propagate ripple carries over next 3 regs
        movwf   count             ;
        call    ripple_carry      ; This function handles it
        decf    FSR,f             ; Adjust FSR to point to
        decf    FSR,f             ; vfo_X_1
                                  ;
        movf    fstep_1,w         ; Get the next increment byte
        addwf   INDF,f            ; Add it to the next higher byte
        movlw   2                 ; Propagate any ripple carries into
        movwf   count             ; the next two registers
        call    ripple_carry      ;
        decf    FSR,f             ; Point to vfo_X_2
                                  ;
        movf    fstep_2,w         ; Get the next to most significant increment
        addwf   INDF,f            ; Add it to the freq byte
        incf    FSR,f             ; Point to vfo_X_3
        btfss   STATUS,C          ; Any carry?
        goto    add_step1         ; No, add last byte
        incf    INDF,f            ; Ripple carry up to the highest byte
add_step1
        movf    fstep_3,w         ; Get the most significant increment byte
        addwf   INDF,f            ; Add it to the most significant freq
        return                    ; Return to the caller


; *****************************************************************************
;
;    Function: ripple_carry
;
;    Purpose: This function propagates carries that result from addition
;             into the higher order bytes of the frequency registers
;
;    Input:   FSR points to the register which was just added,
;             temp is cleared by the caller
;             count is set to the number of higher order bytes to process
;
;    Output:  Any carry resulting from an addition is propagated into the
;             number of higher order bytes specified by count
;
;    Revisions:
;             Rev 2.0 Created for use with dual VFOs. 4-7-04 W3CD
;
; *****************************************************************************

ripple_carry
        rlf     temp,w            ; Put carry from previous add into temp LSB
        incf    FSR,f             ; Point to next freq reg.
        addwf   INDF,f            ; Ripple any carry into next freq reg
        decfsz  count,f           ; Decrement loop count
        goto    ripple_carry      ; Loop until all registers are accounted for
        return                    ; Back to caller

;
; *****************************************************************************
;
;    Function: check_add
;
;    Purpose:  Check if freq exceeds the upper limit.
;
;    Input:    vfo_select, vfo_a and vfo_b registers
;
;    Output:   If freq is below the limit, it is unchanged. Otherwise, it
;              is set to equal the upper limit.
;
;    Revisions:
;              Rev 2.0: Modified to use dual VFOs. 4-7-04 W3CD
;
; *****************************************************************************

check_add
                                  ; First, get the address of vfo_X_3
        movlw   TABLE_ADJUST      ; Offset to 4th element
        addwf   vfo_pointer,w     ; Add in the pointer to vfo_X_0
        movwf   FSR               ; Put this into FSR for indirect addressing
;
;       Check the most significant byte.
;
        movlw   limit_3           ; Get high limit value
        subwf   INDF,w            ; Subtract the limit value
        btfss   STATUS,C          ; Are we at the limit for the byte?
        goto    check_exit        ; No, below.  Checks are done.
        btfss   STATUS,Z          ; Are the bytes equal?
        goto    set_max           ; No, so vfo_X_3 > limit_3.
;
;       Check the second most significant byte when MSB equals limit_3
;
        decf    FSR,f             ; Point to 2nd MSB
        movlw   limit_2           ; Second limit byte
        subwf   INDF,w            ; Subtract limit value
        btfss   STATUS,C          ; Are we at the limit for the byte?
        goto    check_exit        ; No, below.  Checks are done.
        btfss   STATUS,Z          ; Might they be equal?
        goto    set_max           ; Nope, so vfo_X_2 > limit_2
;
;       Check the third most significant byte.
; 
        decf    FSR,f             ; Point to 3rd MSB
        movlw   limit_1           ; Third limit byte
        subwf   INDF,w            ; Subtract limit value
        btfss   STATUS,C          ; Are we at the limit for the byte?
        goto    check_exit        ; No, below.  Checks are done.
        btfss   STATUS,Z          ; Check if the bytes are equal
        goto    set_max           ; No, so vfo_X_1 > limit_1
; 
;       Check the least significant byte.
;
        decf    FSR,f             ; Point to LSB
        movlw   limit_0           ; Fourth limit byte
        subwf   INDF,w            ; Subtract limit value
        btfss   STATUS,C          ; Are we at the limit for the byte?
        goto    check_exit        ; No, below.  Checks are done.
                                  ; Else, it's freq is over limit
set_max
        movf    vfo_pointer,w     ; Get pointer to vfo_X_0
        movwf   FSR               ; into indirect addressing reg.
                                  ;
        movlw   limit_0           ; Get low byte of limit
        movwf   INDF              ; Store in vfo_X_0

        incf    FSR,f             ; Point to vfo_X_1
        movlw   limit_1           ; Get next limit byte
        movwf   INDF              ; Store it in vfo_X_1

        incf    FSR,f             ; Point to vfo_X_2        
        movlw   limit_2           ; Get the next limit byte
        movwf   INDF              ; Store it in vfo_X_2

        incf    FSR,f             ; Point to vfo_X_3
        movlw   limit_3           ; Get the high byte of limit
        movwf   INDF              ; Store it in vfo_X_3

check_exit
        return                    ; Return to the caller


; *****************************************************************************
;
;    Function: sub_step
;
;    Purpose:  Subtract the increment step from freq, checking that it does
;              not go below zero.
;
;    Input:    The values in fstep and freq.
;
;    Output:   The updated value in freq.
;
;    Revisions:
;              Modified to use dual VFOs. 4-7-04 W3CD 
;
; *****************************************************************************

sub_step
        call    invert_fstep      ; Invert fstep_3..0 to perform the subtraction
        call    add_step          ; Add the complement to do the subtraction

                                  ; Determine which VFO is active
        movlw   TABLE_ADJUST      ; Get offset to 4th element
        addwf   vfo_pointer,w     ; Get address of vfo_X_3
        movwf   FSR               ; Put this into FSR for indirect addressing

        btfss   INDF,MSB          ; Is high order frequency byte "negative"?   
        goto    sub_step_exit     ; No, keep going
                                  ; Else, set it to zero.
set_min
        clrf    INDF              ; Clear vfo_X_3
        decf    FSR,f             ; Next byte
        clrf    INDF              ; Clear vfo_X_2
        decf    FSR,f             ; Next byte
        clrf    INDF              ; Clear vfo_X_1
        decf    FSR,f             ; Last byte
        clrf    INDF              ; Clear vfo_X_0

sub_step_exit
        call    invert_fstep      ; Restore the original fstep value
        return                    ; Return to the caller


;*****************************************************************************
;
;    Function:  invert_fstep
;
;    Purpose:   Support function for sub_step and calibrate. This function
;               negates the value of fstep_3..0 to assist in the frequency
;               decrement. This operation is performed twice by sub_step, and
;               is also used by calibrate.
;
;    Input:     fstep_3, fstep_2, fstep_1, fstep_0
;
;    Output:    fstep_3..0 contain the 2's complement of their original value
;
;    Revisions:
;               Made an indepndent function. 4-7-04 W3CD 
;
; *****************************************************************************

invert_fstep
                                  ; Invert the bits in
        comf    fstep_0,f         ; fstep_0
        comf    fstep_1,f         ;   fstep_1
        comf    fstep_2,f         ;      fstep_2
        comf    fstep_3,f         ;        fstep_3
        incfsz  fstep_0,f         ; Now incremnt fstep_0 to get 2's complement
        goto    invert_done       ; If fstep_0 > 0, then done
                                  ; Else, there was a carry out of fstep_0
        incfsz  fstep_1,f         ; Add 1 to fstep_1
        goto    invert_done       ; If fstep_1 > 0, then done
                                  ; Else, there was a carry out of fstep_1
        incfsz  fstep_2,f         ; Add 1 to fstep_2
        goto    invert_done       ; if fstep_2 > 0, then done
                                  ; Else, there was a carry out of fstep_2
        incf    fstep_3,f         ; Increment the high byte

invert_done
       return                     ; Back to caller

;
;*****************************************************************************
;
;   Function: poll_encoder
;
;   Purpose:  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 PIC-EL mechanical shaft encoder debounce and
;                      detent processing is so enabled.
;                   3. Reads PB_2 and determines if a band change is
;                      requested.
;
;    Input:  Knob input read from port A
;            PB_2
;            ren_old -> the last encoder bits read
;            last_dir -> the last direction moved
;
;    Output: ren_read: The contents of PORTA when the encoder was moved
;            ren_new:  The current encoder bits
;            last_dir: The last direction (0 = down, 2 = up [AKA UP_BIT])
;
;    Revisions:
;
;            Ver 2.0: Modified to accomodate detented PIC-EL encoder., et. al.
;                     3-31-04 W3CD
;
;*****************************************************************************
;

poll_encoder
        call    process_pb        ; Check if there is pushbutton activity
        movf    PORTA,w	          ; Get the current encoder value
        movwf   ren_read          ; Save it

;!!!IFDEF DETENT_ENCODER
        call    wait_1ms          ; debounce time
        movf    PORTA,w           ; read the port again
        xorwf   ren_read,w        ; Compare with previous value
        btfss   STATUS,Z          ; Are they equal?
        goto    poll_encoder      ; Poll again if not
;!!! ENDIF

        movlw   ENCODER_BITS      ; Get encoder mask (to isolate RA0 and RA1)
        andwf   ren_read,w        ; Isolated encoder bits into W
        movwf   ren_new           ; Save new value
        xorwf   ren_old,w         ; Has it changed?
        btfsc   STATUS,Z          ; Check zero-flag (zero if no change)
        goto    poll_encoder      ; No change, keep looking until it changes
                                  ; Else, Zero-flag is not set, so continue on 

; 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
        bcf     STATUS,C          ; Clear the carry bit to prepare for rotate P
        rlf     ren_old,f         ; Rotate old bits left to align "Right-Bit" P
        movf    ren_new,w         ; Set up new bits in W                      P
        xorwf   ren_old,f         ; XOR old (left shifted) with new bits      P
        movf    ren_old,w         ; Put XOR results into W also               P
        andlw   UP_BIT            ; Mask to look at only "Left-Bit" of pair   P
        movwf   next_dir          ; Save result (in W) as direction (bit=UP)  P
        xorwf   last_dir,w        ; See if direction is same as before        P
;
;       Prevent encoder slip from giving a false change in direction.
;

;!!! IFNDEF DETENT_ENCODER
;!!!        btfsc   STATUS,Z          ; Zero flag set? (i.e, is direction same?)  P
;!!!        goto    pe_continue       ; Yes, same direction so no slip; keep goingP
;!!!        movf    next_dir,w        ; No Zero-flag, so direction changed        P
;!!!        movwf   last_dir          ; Update the direction indicator            P
;!!!        movf    ren_new,w         ; Save the current encoder bits (now in W)  P
;!!!        movwf   ren_old           ;   for next time                           P
;!!!        goto    poll_encoder      ; Try again
;!!! ENDIF

pe_continue
        clrf    last_dir          ; Clear last_dir (default is DN)            P
        btfsc   ren_old,DIR_BIT   ; Are we going UP?                          P
        goto    pe_up             ; Yes, go process it.
                                  ; Else, we are goiong down
;!!! IFDEF DETENT_ENCODER
        movf    ren_new,w         ; Get the current encoder bits
        movwf   ren_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
;!!! ENDIF
        goto    pe_movement       ; Indicate that the encoder has moved

pe_up 
        movlw   UP_BIT            ; Get UP value
        movwf   last_dir          ;   and set in last_dir

;!!! IFDEF DETENT_ENCODER
        movf    ren_new,w         ; Get the current encoder bits
        movwf   ren_old           ; Save them in ren_old for the next time
        incf    enc_counter,f     ; Increment the encoder counter
        btfsc   enc_counter,0     ; Return only on modulo 4 counts
        goto    poll_encoder      ;
        btfsc   enc_counter,1
        goto    poll_encoder      ; Loop again if not modulo 4
;!!! ENDIF

pe_movement                       ; Arrive here when encoder is being turned

        movf    ren_new,w         ; Get the current encoder bits
        movwf   ren_old           ; Save them in ren_old for the next time
                                  ;
        btfsc   ren_read,PB_2     ; Check if PB_2 is pressed
        goto    pe_no_band        ; Skip if it's not pressed.
                                  ; Else, we're in Band Mode
        bsf    PB_flags,BAND_MODE ; Set the appropriate flag bit to
                                  ; prevent normal PB_2 processing.
;!!!        movlw   LED3_ON           ; Turn on LED3 to indicate band mode
;!!!        andwf   led_states,f      ; Update the states
        goto    pe_exit           ; Finish up

pe_no_band
        bcf    PB_flags,BAND_MODE ; Clear the band mode flag
;!!!        movlw   LED3_OFF          ; Clear the band mode LED and exit
;!!!        iorwf   led_states,f      ; Update led_states
        
pe_exit
;!!!        call    set_leds          ; turn it on
        return                    ; Return to the caller


;****************************************************************************
;
;    Function:  process_pb
;
;    Purpose:   This function examines the state of PB_1, PB_2 and PB_3
;               and has mulitple operating characteristics.
;
;               1. Pressing and releasing either button individually changes
;                  the cursor position on the display and adjusts the frequency
;                  step to another decade. The cursor and frequency step wrap
;                  around the 10MHz position, returning the frequency increment
;                  back to 1Hz.
;
;               2. If either PB1 or PB_2 (but not both) is held down for more
;                  than the specified delay, (1 second) auto repeat is enabled
;                  where the decade and cursor position are updated continuously
;                  until the button is released. The update rate is 4 times/sec.
;
;               3. If both PB_1 and PB_2 are held down for more than 2 seconds,
;                  then the EEPROM contents are updated. The data written
;                  depends on whether the user is changing bands or changing
;                  the frequency.
;
;               4. If PB_3 is pressed then a VFO action is performed, depending
;                  on the state of PB_1.
;
;    Inputs:    PORTA bits PB_1, PB_2, PB_3 and PB_flags
;
;    Outputs:   If a button is pressed, then various control variables are
;               updated. The function exits if no buttons are pressed.
;
;    Revisions:
;
;		Ver 2.0: Updated 3-27-04 by W3CD. All of the aforementioned
;                features were added while the PICELgen Rev 1.4
;                features were removed.
;
;****************************************************************************

process_pb
        btfsc	PB_flags,PCAL_FLG   ; Check if in calibrate mode
        goto	process_pb_exit     ; If bit is set, skip processing
                                    ; Else, examine the button status

;!!! IFDEF DUAL_VFO                      ; If Dual VFO mode is enabled...
        btfss   PORTA,PB_3          ; Check for VFO change activity
        goto    process_pb_3        ; If so, then handle vfo request
;!!! ENDIF

        btfsc   PORTA,PB_1          ; Is PB_1 being pressed?              
        goto    pb_2_check          ; Check PB_2 if not.
                                    ; Else, there's further processing to do
        btfss   PORTA,PB_2          ; Check PB_2 to see if both are pressed
        goto    process_both        ; If it's active, process both


process_pb_1                        ; Arrive here to process just PB_1
        movlw   PB_RPT_WAIT         ; Setup the repeat delay timer
        movwf   PB_wait_count       ; 
        bcf     PB_flags,PB_RPT_FLG ; Clear repeat flag

pb_1_release_wait
        call    wait_32ms           ; Wait a bit before checking again
        btfsc   PORTA,PB_1          ; Is PB_1 still behing held?
        goto    pb_1_released       ; If it's released, adjust decade and exit

        btfss   PORTA,PB_2          ; Else, check for a late pressing of PB_2
        goto    process_both        ; If PB_2 was pressed, then process both

;!!! IFDEF DUAL_VFO                      ; If Dual VFO mode is enabled...
        btfss   PORTA,PB_3          ; Check for VFO change activity
        goto    process_pb_3        ; If so, then handle vfo request
;!!! ENDIF
                                    ; Else, test if repeat delay has expired
        decf    PB_wait_count,f     ; Decrement wait period
        btfss   STATUS,Z            ; Check the Zero bit
        goto    pb_1_release_wait   ; Keep looping until something happens
                                    ; Else, repeat delay has expired.
        movlw   DECADE_INCREMENT    ; Increase decade adjustment
        call    update_decade       ; Bump the decade-related variables

        movlw   PB_RPT_DLY          ; Load the repeat interval value
        movwf   PB_wait_count       ; into PB_wait_count
        bsf     PB_flags,PB_RPT_FLG ; update when PB_1 is released
        goto    pb_1_release_wait   ; Loop some more

pb_1_released
        movlw   DECADE_INCREMENT    ; Increase decade adjustment
        goto    process_pb_release  ; common code for pushbuttons


;******************************************************************************
;
;	Arrive here when PB_1 was not pressed.
;	Check for PB_2 activity.
;
;******************************************************************************
        
pb_2_check
        btfss   PB_flags,BAND_MODE  ; Check if poll_encoder detected band change
        goto    pb_2_normal         ; If not, then do normal processing
                                    ; Else, in Band Mode
        btfss   PORTA,PB_2          ; Is PB_2 still being pressed?
        goto    process_pb_exit     ; If so, then exit
                                    ; Else, it was released. Do housekeeping.
        clrf    PB_flags            ; Clear the PB flags
;!!!        movlw   LED3_OFF            ; Turn off the band mode indicator
;!!!        iorwf   led_states,f        ; Set the led state bit
;!!!        call    set_leds            ; Turn LED3 off
        goto    process_pb_exit     ; Skip other processing.

pb_2_normal                         ; Regular handling of PB_2
        btfsc   PORTA,PB_2          ; Is PB_2 being pressed?              
        goto    process_pb_exit     ; Bail out if not.
                                    ; Else, process PB_2 pressing
        btfsc   PB_flags,PB_2_DLY   ; If already in a repeat delay,
        goto    pb_2_release_wait   ; Then skip the preliminaries
                                    ; Else, this is the first time through
        movlw   PB_RPT_WAIT         ; Setup the repeat delay timer
        movwf   PB_wait_count       ; 
        bcf     PB_flags,PB_RPT_FLG ; Clear repeat flag
        bsf     PB_flags,PB_2_DLY   ; Set the PB2 count down flag

pb_2_release_wait                   ; Wait for operator to do something
        call    wait_32ms           ; Linger a while before checking again
        btfsc   PORTA,PB_2          ; Is PB_2 still behing held?
        goto    pb_2_released       ; If it's released, adjust decade and exit
                                    ;
       	btfss   PORTA,PB_1          ; Else, check for a late pressing of PB_1
        goto    process_both        ; If PB_1 was pressed, then process both
                                    ; Else, test if repeat delay has expired
        decf    PB_wait_count,f     ; Decrement wait period
        btfsc   STATUS,Z            ; Check the Zero bit
        goto    pb_2_timeout        ; If it's set, then timeout occurred and
                                    ; it's time to adjust the decade
                                    ; Else, test if we're still waiting for
        btfsc   PB_flags,PB_2_DLY   ; initial timeout before repeating
        goto    process_pb_exit     ; Exit if this is the case.
        goto    pb_2_release_wait   ; Else, we're in a repeat loop

pb_2_timeout                        ; Arrive here when a delay has expired.
        bcf     PB_flags,PB_2_DLY   ; Clear the delay flag
        movlw   DECADE_DECREMENT    ; Decrease decade adjustment
        call    update_decade       ; Bump the decade-related variables

        movlw   PB_RPT_DLY          ; Load the repeat interval value
        movwf   PB_wait_count       ; into PB_wait_count
        bsf     PB_flags,PB_RPT_FLG ; update when PB_2 is released
        goto    pb_2_release_wait   ; Loop some more

pb_2_released
        movlw   DECADE_DECREMENT    ; Load up decade adjustment
                                    ; And continue with common PB code

;************************************************************************
;
;	Common code to handle the release of either PB_1 or PB_2 in the
;	decade adjust mode. Wreg contains the cursor adjustment value.
;
;************************************************************************

process_pb_release

        btfsc   PB_flags,PB_RPT_FLG ; If repeat flag was set,
        goto    pb_cf_exit          ; don't do the final adjustment.
        call    update_decade       ; Else, adjust decade of interest
        goto    pb_cf_exit          ; Clear the flags & QRT this function


;************************************************************************
;
;	Arrive here when both PB_1 and PB_2 were pressed. There are two
;   ways this funcion operates, depending on the the state of the
;   band_mode bit in PB_flags. If this bit is cleared, then PB2 was
;   pressed and the shaft encoder was not turned. If PB2 was pressed and
;   the shaft encoder was rotated before the PB2 repeat timeout expired,
;   then the user is accessing the band memories in EEPROM and, as a
;   result, the band_mode bit is set. This bit stays set until PB2 is
;   released. This code section exploits this operation.
;
;   In band frequency update mode, the code updates the current band's
;   frequency in EEPROM and then displays which band was updated before
;   reverting back to the normal frequency display.
;
;   If PB1 is pressed and held while in band change mode, the default
;   startup band location in EEPROM is written with the current band.
;   A message is displayed to alert the user of this fact.
;
;************************************************************************

process_both
        movlw   PB_INIT_FREQ_WAIT   ; Setup the repeat delay timer
        movwf   PB_wait_count       ; 

both_release_wait
                                    ; Wait for the PBs to be released
        call    wait_32ms           ; Use our favorite delay
        btfsc   PORTA,PB_1          ; If PB_1 was released,
        goto    process_pb_exit     ; Then exit before new freq set
        btfsc   PORTA,PB_2          ; Check PB_2 for same condition
        goto    process_pb_exit     ; Quit if operator released it

                                    ; Else, both buttons are still held
        decf    PB_wait_count,f     ; Update hold timer
        btfss   STATUS,Z            ; Check the Zero bit
        goto    both_release_wait   ; Loop if there's still more time
                                    ; Else, 2 seconds are up.
                                    ; The user desired an EEPROM update
                                    ; Determine what will be updated.
        btfss   PB_flags,BAND_MODE  ; Check if it's default startup band
        goto    update_band_freq    ; Nope, it's the current band freq.
                                    ; Else, it is the power-up band
        call    update_EEPROM_band  ; Write the new startup band
                                    ;
        call    clear_lcd           ; Clear the display
        movlw	upd_msg_offset      ; Display the update
        call    display_message     ;  message
        call    wait_512ms          ; Show this for 1/2 sec

        call    clear_lcd           ; Clear the display
        movlw   init_msg_offset     ; Point to new startup band msg
        call    display_message     ; Display it on LCD
        call    wait_a_sec          ; for 1 second
        goto    pb_update_exit      ; Done!

update_band_freq                    ; Arrive here to update freq.

        call    update_EEPROM_freq  ; update the current band frequency		

        call    clear_lcd           ; Now display a message on LCD
                                    ; to inform operator of update
                                    ; so he releases the buttons!
        movlw   upd_msg_offset      ; Point to the message
        call    display_message     ; Display it
        call    show_band           ; Display band being modified
        call    wait_a_sec          ; Show it for 1 second before

pb_update_exit
        call    show_freq           ; revert back to usual display

pb_cf_exit                          ; Arrive here to clear flags and exit
        clrf    PB_flags            ; clear all of the PB flags

process_pb_exit
        return                      ; Back to caller


;******************************************************************************
;
;	Arrive here when PB_3 was pressed, indicating some sort of vfo action
;
;******************************************************************************

;!!! IFDEF DUAL_VFO

process_pb_3

        call    wait_32ms           ; Use our favorite delay
        btfss   PORTA,PB_1          ; If PB_1 was not pressed, then skip
        bsf     PB_flags,VFO_ACTION ; Else, set flag that VFO A->B copy
                                    ; was requested
        btfss   PORTA,PB_3          ; If PB_3 was released, then process
        goto    process_pb_3        ; Loop until operator releases PB_3
                                    ; Arrive here after PB_3 was released.
        btfss   PB_flags,VFO_ACTION ; If no vfo copy, then skip
        goto    swap_vfo            ; Else, swap VFOs
                                    ; Copy VFO A to VFO B, regardless of
        call    copy_a_to_b         ; which one is active at the time.

        goto    process_pb3_exit    ; All done.

swap_vfo
        btfss   vfo_select,VFO_SEL  ; Determine which vfo was active
        goto    vfo_a               ; If VFO A then skip
                                    ; Else, VFO B was active
        bcf     vfo_select,VFO_SEL  ; Select VFO A
        movlw   vfo_a_0             ; Get address of vfo_A_0
        movwf   vfo_pointer         ; Set up vfo pointer register

;!!!        movlw   LED2_OFF            ; Turn off VFO B indicator
;!!!        iorwf   led_states,f
;!!!        movlw   LED1_ON             ; Turn on VFO A indicator
;!!!        andwf   led_states,f
        goto    process_pb3_exit    ; Use the common exit

vfo_a                               ; Change VFO setting to B
        bsf     vfo_select,VFO_SEL  ; Select VFO B
        movlw   vfo_b_0             ; Get address of vfo_b_0
        movwf   vfo_pointer         ; Set up vfo pointer register

;!!!        movlw   LED1_OFF            ; Turn off VFO A indicator
;!!!        iorwf   led_states,f
;!!!        movlw   LED2_ON             ; Turn on VFO B indicator
;!!!        andwf   led_states,f

process_pb3_exit                    ; Finish up PB_3 handling
        clrf    PB_flags            ; Clear all PB flags
        call    show_freq           ; Display the frequency on the LCD
        call    send_dds_word       ; Send the control word to the DDS chip
        goto    process_pb_exit     ; Use the common exit


;****************************************************************************
;
;    Function:  copy_a_to_b
;
;    Purpose:   This function copies the contents of the VFO A registers
;               into the VFO B registers
;
;    Inputs:    vfo_a_band, vfo_a_0, vfo_a_1, vfo_a_2, vfo_a_3
;
;    Outputs:   vfo_b_band, vfo_b_0, vfo_b_1, vfo_b_2, vfo_b_3
;
;    Revisions:
;               Ver 2.0: Created 4-7-04 by W3CD.
;
;****************************************************************************


copy_a_to_b
        movf    vfo_a_0,w           ; Copy all 4 bytes of VFO A
        movwf   vfo_b_0             ;  into VFO B
        movf    vfo_a_1,w
        movwf   vfo_b_1
        movf    vfo_a_2,w
        movwf   vfo_b_2
        movf    vfo_a_3,w
        movwf   vfo_b_3
        movf    vfo_a_band,w        ; Also copy VFO A band information
        movwf   vfo_b_band          ; into VFO B
        return
;!!! ENDIF


;****************************************************************************
;
;    Function: update_decade
;
;    Purpose:  Support function for PB_1_check. Adjusts the cursor position
;              and decade table pointer. It also calls get_decade to update
;              the values of fstep_3 through fstep_0. Lastly, it calls
;              show_freq to update the cursor position on the LCD.
;
;    Inputs:   W contains the decade adjustment.
;
;    Outputs:  cur_pos is modified, modulo 8 (0 to 7)
;              decade is modified
;              fsetp_3..0 are modified in the sequence of:
;              1Hz, 10Hz, 100Hz, 1KHz, 10KHz, 100KHz, 1MHz, 10 MHz, 1Hz, etc.,
;              depending on direction.
;
;    Revisions:
;              Ver 2.0: Created 3-27-04 by W3CD.
;
;****************************************************************************

update_decade

        addwf   decade,w            ; Add the decade adjustment
        andlw   DECADE_MASK         ; Only using 8 decades here and the
                                    ; decades wrap around the 10MHz position
        movwf   decade              ; Store updated decade
        call    cursor_table        ; Get the translated cursor position
        movwf   cur_pos             ; Save the updated cursor value.
        call    get_decade          ; Update fstep_3..0
        call    show_freq           ; update display to show new cursor position
        return                      ; Return to the caller


;****************************************************************************
;
;    Function:  get_decade
;
;    Purpose:   Support function which sets new increment values for
;               fstep_3 through fstep_0 using values in decade_table
;
;    Inputs:    decade
;
;    Outputs:   fstep_3, fstep_2, fstep_1 and fstep_0 are updated
;
;    Revisions:
;               Ver 2.0: Added 3-27-04 by W3CD.
;
;****************************************************************************

get_decade

        movlw   FREQ_COUNT          ; There are four bytes to copy
        movwf   count               ; Set up count variable
        movlw   fstep_3             ; Get target address
        movwf   FSR                 ; Store in pointer register
        movf    decade,w            ; Get the decade index
        movwf   temp                ; Copy it to temp variable
        bcf     STATUS,C            ; Make sure Carry bit is clear
        rlf     temp,f              ; Shift decade index 2 place to left
        rlf     temp,f              ; to develop table index

get_decade_loop
        movf    temp,w              ; Get the decade pointer into W
        call    decade_table        ; Get a decade byte
        movwf   INDF                ; Store it
        decf    FSR,f               ; Point to next lowest fstep byte
        incf    temp,f              ; Increment decade table pointer
        decfsz  count,f             ; Decrement loop count
        goto    get_decade_loop     ; loop while more bytes remain
                                    ;
        return                      ; Exit when done

;
; *****************************************************************************
;
;    Function: calibrate
;
;    Purpose:  This routine is entered at start up if the calibrate
;              push button is (PIC-EL PB_1) is pressed at power-on time.
;
;              If a 8-character LCD is used,"10000000" is displayed
;              If a 16-character LCD is used," 10,000.000 CAL " is displayed
;
;              The DDS chip is programmed to produce 10 MHz, based on the
;              osc value stored in the EEPROM.  As long as the button is
;              pressed, the osc value is slowly altered to allow the output
;              to be trimmed to exactly 10 MHz.  Once the encoder is turned
;              after the button is released, the new osc value is stored in
;              the EEPROM and normal operation begins.
;
;    Input:    The original osc constant in EEPROM
;
;    Output:   The corrected osc constant in EEPROM
;
;    Revisions:
;              Rev 2.0: Deleted EEPROM osc byte reads since the init code
;                       was changed to read them; this saves program memory
;                       space. Modified for use with dual vfos. 4-8-04 W3CD
;
; *****************************************************************************

calibrate
        bsf     PB_flags,PCAL_FLG ; Set the calibrate bit to prevent cursor movement

        movlw   cal_freq_0        ; Set frequency to 10MHz by setting freq
        movwf   vfo_a_0           ;   to the binary equivalent of 10 MHz
        movlw   cal_freq_1        ;    
        movwf   vfo_a_1           ;    
        movlw   cal_freq_2        ;    
        movwf   vfo_a_2           ;   
        movlw   cal_freq_3        ;   
        movwf   vfo_a_3           ;   

;
; The starting reference oscillator values have already been read
; from EEPROM. Convert reference freq and display it.
;
        call    show_freq         ; Display the frequency on the LCD

;!!! IF LCDCHAR == 16

;!!! IF LCD16_LINEAR == 0
;!!!        movlw   PICEL_LONG_9 + 4  ; Point LCD at position 13 for AmQRP 16x1 LCD
;!!! ELSE
 ;       movlw CMD_MOV_CUR_DISP+12 ; Short jump to LCD position 13
;!!! ENDIF
;        call    cmnd2LCD          ; Position the cursor
;        movlw   cal_offset        ; Display "CAL" on LCD
;        call    display_message   ; (position 16 is blank)

;!!! ENDIF                             ; 16 Character LCDs

cal_loop
        call    send_dds_word     ; Update the DDS chip
        call    poll_encoder      ; Wait until the encoder has moved.
        clrf    fstep_3           ; Clear the three most significant
        clrf    fstep_2           ;   bytes of fstep
        clrf    fstep_1           ; 
        movlw   SMALL_FSTEP       ; Assume that we are adjusting slowly
        movwf   fstep_0           ; Use small increment
        btfsc   ren_read,PB_3     ; Was the encoder changing slowly?
        goto    update_osc        ; Yes, then continue with small increment
        movlw   LARGE_FSTEP       ; No, then use the large increment
        movwf   fstep_0           ;
update_osc
        nop                       ; Wait a cycle
        btfsc   last_dir,DIR_BIT  ; Are we moving down?
        goto    faster            ; No, increase the osc value
;
;       slower
;
        call    invert_fstep      ; Use this handy function to complement fstep

faster
        movf    fstep_0,w         ; Get the low byte increment
        addwf   osc_0,f           ; Add it to the low osc byte
        btfss   STATUS,C          ; Was there a carry?
        goto    add4              ; No, add the next bytes
        incfsz  osc_1,f           ; Ripple carry up to the next byte
        goto    add4              ; No new carry, add the next bytes
        incfsz  osc_2,f           ; Ripple carry up to the next byte
        goto    add4              ; No new carry, add the next bytes
        incf    osc_3,f           ; Ripple carry up to the highest byte
add4
        movf    fstep_1,w         ; Get the second byte increment
        addwf   osc_1,f           ; Add it to the second osc byte
        btfss   STATUS,C          ; Was there a carry?
        goto    add5              ; No, add the third bytes
        incfsz  osc_2,f           ; Ripple carry up to the next byte
        goto    add5              ; No new carry, add the third bytes
        incf    osc_3,f           ; Ripple carry up to the highest byte
add5
        movf    fstep_2,w         ; Get the third byte increment
        addwf   osc_2,f           ; Add it to the third osc byte
        btfss   STATUS,C          ; Was there a carry?
        goto    add6              ; No, add the fourth bytes
        incf    osc_3,f           ; Ripple carry up to the highest byte
add6
        movf    fstep_3,w         ; Get the fourth byte increment
        addwf   osc_3,f           ; Add it to the fourth byte
        btfss   PORTA,PB_1        ; Tuning-increment pushbutton pressed?
        goto    cal_loop          ; Yes, stay in calibrate mode
                                  ; Else, calibration is complete.
        clrf    ee_addr           ; Write final value to EEPROM.
        movlw   osc_0             ; Get starting address of cal values
        movwf   FSR               ; Store in pointer
        call    update_EEPROM_cal ; Write contents to EEPROM.
        bcf     PB_flags,PCAL_FLG ; Clear the calibrate bit
        return                    ; Return to the caller

;                                                                             P
; *****************************************************************************
; *                                                                           *
; * Purpose:  Multiply the 32 bit number for oscillator frequency times the   *
; *           32 bit number for the displayed frequency.                      *
; *                                                                           *
; *                                                                           *
; *   Input:  The reference oscillator value in osc_3 ... osc_0 and the       *
; *           current frequency stored in freq_3 ... freq_0.  The reference   *
; *           oscillator value is treated as a fixed point real, with a 24    *
; *           bit mantissa.                                                   *
; *                                                                           *
; *  Output:  The result is stored in AD9850_3 ... AD9850_0.                  *
; *                                                                           *
; *****************************************************************************
;

calc_dds_word
        clrf    AD9850_0          ; Clear the AD9850 control word bytes
        clrf    AD9850_1          ; 
        clrf    AD9850_2          ; 
        clrf    AD9850_3          ; 
        clrf    AD9850_4          ; 
        movlw   0x20              ; Set count  to 32   (4 osc bytes of 8 bits)
        movwf   mult_count        ; Keep running count
        movf    osc_0,w           ; Move the four osc bytes
        movwf   osc_temp_0        ;   to temporary storage for this multiply
        movf    osc_1,w           ; (Don't disturb original osc bytes)
        movwf   osc_temp_1        ; 
        movf    osc_2,w           ; 
        movwf   osc_temp_2        ; 
        movf    osc_3,w           ; 
        movwf   osc_temp_3        ; 
                                  ; Now develop the active vfo frequency pointer
        movf    vfo_pointer,w     ; Get pointer to vfo_X_0
mult_loop
        movwf   FSR               ; Store vfo_X_0 pointer in FSR for indirect addr.
        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    INDF,w            ; Yes, get the vfo_X_0 term
        addwf   AD9850_1,f        ;   and 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
        incf    FSR,f             ; Point to vfo_X_1
        movf    INDF,w            ; Use the vfo_X_1 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
        incf    FSR,f             ; Point to vfo_X_2
        movf    INDF,w            ; Use the 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
        incf    FSR,f             ; Point to vfo_X_3
        movf    INDF,w            ; Use the vfo_X_3 term
        addwf   AD9850_4,f        ; Add freq term to total in correct position
noAdd
        rrf     AD9850_4,f        ; Shift next multiplier bit into position
        rrf     AD9850_3,f        ; Rotate bits to right from byte to byte
        rrf     AD9850_2,f        ; 
        rrf     AD9850_1,f        ; 
        rrf     AD9850_0,f        ; 
        rrf     osc_temp_3,f      ; Shift next multiplicand bit into position
        rrf     osc_temp_2,f      ; Rotate bits to right from byte to byte
        rrf     osc_temp_1,f      ; 
        rrf     osc_temp_0,f      ; 

        movf    vfo_pointer,w     ; Get the pointer to vfo_X_0
        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          ; Yes, clear _4.  Answer is in bytes _3 .. _0
       movlw   0x01              ; Turn on 6x clock multiplier (AD9851)Added TM 26/2/12 (comment out for AD9850)
       movwf   AD9850_4          ; Last byte to be sent Added TM 26/2/12 (comment out for AD9850)

        return                    ; Done.


;
; *****************************************************************************
; *                                                                           *
; * Purpose:  This routine sends the AD9850 control word to the DDS chip      *
; *           using a serial data transfer.                                   *
; *                                                                           *
; *   Input:  AD9850_4 ... AD9850_0                                           *
; *                                                                           *
; *  Output:  The DDS chip register is updated.                               *
; *                                                                           *
; *****************************************************************************
;
send_dds_word
        call    calc_dds_word     ; calculate the data to send to AD9850
send_dds_word1
        movlw   AD9850_0          ; Point FSR at AD9850
        movwf   FSR               ; 
next_byte
        movf    INDF,w            ; 
        movwf   byte2send         ; 
        movlw   0x08              ; Set counter to 8
        movwf   bit_count         ; 
next_bit
        rrf     byte2send,f       ; Test if next bit is 1 or 0
        btfss   STATUS,C          ; Was it zero?
        goto    send0             ; Yes, send zero
        bsf     PORTB,DDS_dat     ; No, send one
        bsf     PORTB,DDS_clk     ; Toggle write clock
        bcf     PORTB,DDS_clk     ;
        goto    break             ; 
send0
        bcf     PORTB,DDS_dat     ; Send zero
        bsf     PORTB,DDS_clk     ; Toggle write clock
        bcf     PORTB,DDS_clk     ;
break
        decfsz  bit_count,f       ; Has the whole byte been sent?
        goto    next_bit          ; No, keep going.
        incf    FSR,f             ; Start the next byte unless finished
        movlw   AD9850_4+1        ; Next byte (past the end)
        subwf   FSR,w             ; 
        btfss   STATUS,C          ;
        goto    next_byte         ;
        bsf     PORTB,DDS_load    ; Send load signal to the AD9850
        bcf     PORTB,DDS_load    ;

;!!!        call    set_leds
        return                    ;


;
; *****************************************************************************
; *                                                                           *
; * Purpose:  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 8x1 or a 16x1 display.                   P
; *                                                                           *
; *   Input:  None                                                            *
; *                                                                           *
; *  Output:  None                                                            *
; *                                                                           *
; *****************************************************************************
;

init_LCD
        call    wait_64ms         ; Wait for LCD to power up
        movlw   CMD_RESET_LCD     ; LCD init instruction (First)
        movwf   PORTB             ; Send to LCD via RB3..RB0
        bsf     PORTB,LCD_e       ; Set the LCD E line high,
        call    wait_64ms         ;   wait a "long" time,
        bcf     PORTB,LCD_e       ;   and then Clear E 
        movlw   CMD_RESET_LCD     ; LCD init instruction (Second)
        movwf   PORTB             ; Send to LCD via RB3..RB0
        bsf     PORTB,LCD_e       ; Set E high,
        call    wait_32ms         ;   wait a while,
        bcf     PORTB,LCD_e       ;   and then Clear E 
        movlw   CMD_RESET_LCD     ; LCD init instruction (Third)
        movwf   PORTB             ; Send to LCD via RB3..RB0
        bsf     PORTB,LCD_e       ; Set E high,
        call    wait_32ms         ;   wait a while,
        bcf     PORTB,LCD_e       ;   and then Clear E
        movlw   PICEL_IFC1        ; Set the LCD for 4-bit mode I/O
        movwf   PORTB             ; Send to LCD via RB3..RB0
        bsf     PORTB,LCD_e       ; Set E high,
        call    wait_16ms         ;   wait a while,
        bcf     PORTB,LCD_e       ;   and then Clear E
        movlw   PICEL_IFC2        ; Set operational characteristics of LCD
        call    cmnd2LCD          ; Send command in w to LCD
        movlw   PICEL_DISPLAY_OFF ; Display off, cursor and blink off 
        call    cmnd2LCD          ; Send command to LCD
        movlw   CMD_CLEAR_LCD     ; Clear and reset cursor
        call    cmnd2LCD          ; Send command in w to LCD
        movlw   PICEL_CURSOR_RT   ; Set cursor to move right, no display shift
        call    cmnd2LCD          ; Send command in w to LCD

        movlw   PICEL_DISPLAY_ON  ; Display on, cursor on, blink off
        call    cmnd2LCD          ; Send command to LCD
        return                    ; Done!
;
;****************************************************************************
;
;    Purpose:  Displays power-up message and version information.
;
;    Input:    MCODE_REV_0 through MCODE_REV_4 set up
;
;    Output:   LCD displays debug info
;
; ****************************************************************************

display_version

        movlw	sign_msg_offset   ; Offset to Sign-on message
        call	display_message   ; Display it on LCD

;!!! IF LCDCHAR == 8
        call	wait_a_sec        ; Let it sit a while
        movlw	CMD_MOV_CUR_DISP  ; Home the cursor
        call    cmnd2LCD

;!!! ELSE
;!!! IF LCD16_LINEAR == 0             ; Move to 9th character position
;!!!        movlw   PICEL_LONG_9      ; for AmQRP 16x1 LCD 
;!!!        call    cmnd2LCD          ; Move cursor to appropriate position
;!!! ENDIF
;!!! ENDIF
;        movlw   ver_msg_offset    ; Offset to version message
;        call    display_message   ; Display on the LCD
;        call    wait_a_sec        ; Yet another pause
        return                    ; back to caller



;*******************************************************************************
;
;     Function:	 display_message
;
;     Purpose:   Displays the selected message in message_table on the LCD
;
;     Input:     Wreg contains the offset of the desired message to display
;
;     Output:    Message is displayed on LCD
;
;     Revisions: New in Version 2.0 - W3CD
;
;*******************************************************************************

display_message
        movwf   message_pointer	  ; Save the starting message offset

display_loop
        call    message_table     ; Call the table to return a character
        andlw   FLAG_MASK         ; Just set status flags, Z to be precise.
        btfsc   STATUS,	Z         ; If the char is null, then end of message
        goto    display_exit      ; and exit

        call    data2LCD          ; Else, send the character to the LCD
        incf    message_pointer,f ; Increment offset to next character
        movf    message_pointer,w ; Get it into W
        goto    display_loop      ; Loop again

display_exit
        return                    ; Exit when the message has been sent



;
; *****************************************************************************
;
;    Function:  bin2BCD
;   
;    Purpose:  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.
;
;    Input:    The value in freq_0 ... freq_3
;
;    Output:   The BCD number in BCD_0 ... BCD_4
;
;    Revisions:
;              Rev. 2.0: Modified for use with dual vfos. 4-7-04 W3CD
;
; *****************************************************************************

bin2BCD
        movlw   0x20              ; Set loop counter
        movwf   BCD_count         ;   to 32
        clrf    BCD_0             ; Clear output
        clrf    BCD_1             ;   "     "
        clrf    BCD_2             ;   "     "
        clrf    BCD_3             ;   "     "
        clrf    BCD_4             ;   "     "

;
; Unable to use indirect addressing here, so copy the active VFO
; data to freq3..0 for use by this function
;

        movf   vfo_pointer,w      ; Get vfo_X_0 address
        movwf  FSR                ; Copy address to FSR

        movf   INDF,w             ; Get low byte of VFO frequency
        movwf  freq_0             ; store it for conversion
        incf   FSR,f               ; Point to vfo_X_1
        movf   INDF,w             ; Get vfo_X_1
        movwf  freq_1             ; Store it
        incf   FSR,f              ; Point to vfo_X_2
        movf   INDF,w             ; Get the byte
        movwf  freq_2             ; Store it
        incf   FSR,f              ; Point to vfo_X_3
        movf   INDF,w             ; Get it
        movwf  freq_3             ; Store it
                                  ; Now ready for conversion
bin_loop
        bcf     STATUS,C          ; Clear carry bit in STATUS

;
; Rotate bits in freq bytes.  Move from LS byte (freq_0) to next byte (freq_1).
; Likewise, move from freq_1 to freq_2 and from freq_2 to freq_3.
;
        rlf     freq_0,f          ; Rotate left, 0 -> LS bit, MS bit -> Carry
        rlf     freq_1,f          ; Rotate left, Carry->LS bit, MS bit->Carry
        rlf     freq_2,f          ; Rotate left, Carry->LS bit, MS bit->Carry
        rlf     freq_3,f          ; Rotate left, Carry->LS bit, MS bit->Carry
        btfsc   STATUS,C          ; Is Carry clear? If so, skip next instruction
        bsf     freq_0,0          ; Carry is set so wrap and set bit 0 in freq_0
;
; Build BCD bytes. Move into LS bit of BCD bytes (LS of BCD_0) from MS bit of
; freq_3 via the Carry bit.  
;
        rlf     BCD_0,f           ; Rotate left, Carry->LS bit, MS bit->Carry
        rlf     BCD_1,f           ; Rotate left, Carry->LS bit, MS bit->Carry
        rlf     BCD_2,f           ; Rotate left, Carry->LS bit, MS bit->Carry
        rlf     BCD_3,f           ; Rotate left, Carry->LS bit, MS bit->Carry
        rlf     BCD_4,f           ; Rotate left, Carry->LS bit, MS bit->Carry
        decf    BCD_count,f       ; Decrement loop count
        btfss   STATUS,Z          ; Is loop count now zero?
        goto    adjust            ; No, go to adjust
        return                    ; Yes, EXIT 

; ============================================================================
; Internal subroutine, called by bin2BCD main loop only
; 
; As BCD bytes are being built, make sure the nibbles do not grow larger than 9. 
; If a nibble gets larger than 9, increment to next higher nibble.  
; (If the LS nibble of a byte overflows, increment the MS nibble of that byte.)
; (If the MS nibble of a byte overflows, increment the LS nibble of next byte.)
;
adjust
        movlw   BCD_0             ; Get pointer to BCD_0
        movwf   FSR               ; Put pointer in FSR for indirect addressing
        call    adj_BCD           ; 
        incf    FSR,f             ; Move indirect addressing pointer to BCD_1
        call    adj_BCD           ; 
        incf    FSR,f             ; Move indirect addressing pointer to BCD_2
        call    adj_BCD           ; 
        incf    FSR,f             ; Move indirect addressing pointer to BCD_3
        call    adj_BCD           ; 
        incf    FSR,f             ; Move indirect addressing pointer to BCD_4
        call    adj_BCD           ; 
        goto    bin_loop          ; Back to main loop of bin2BCD
; ============================================================================
adj_BCD  ; Internal subroutine, called by adjust only
        movlw   3                 ; Add 3
        addwf   INDF,w            ;   to LS digit
        movwf   BCD_temp          ; Save in temp
        btfsc   BCD_temp,3        ; Is LS digit + 3 > 7  (Bit 3 set)
        movwf   INDF              ; Yes, save incremented value as LS digit
        movlw   0x30              ; Add 3
        addwf   INDF,w            ;   to MS digit
        movwf   BCD_temp          ; Save as temp
        btfsc   BCD_temp,7        ; Is MS digit + 3 > 7  (Bit 7 set)
        movwf   INDF              ; Yes, save incremented value as MS digit
        return                    ; Return to adjust subroutine
;
; *****************************************************************************
; *                                                                           *
; * Purpose:  Display the frequency setting on the LCD.                       *
; *           If a 1x8 LCD display so display freq in Hz -  e.g. 14025000     P
; *           If a 1x16 LCD display so display freq kHz - e.g  14,025.000 kHz P
; *                                                                           *
; *   Input:  The values in BCD_4 ... BCD_0                                   *
; *                                                                           *
; *  Output:  The number displayed on the LCD                                 *
; *                                                                           *
; *****************************************************************************
;
show_freq

        call    bin2BCD           ; Convert the binary freq. to BCD for display
        movlw   CMD_HOME_CUR_LCD  ; Point the LCD to first LCD digit location
        call    cmnd2LCD          ; Send starting digit location to LCD
;******************************************************************************
;!!! IF LCDCHAR == 16
;!!!        movlw   ' '               ; Send a space
;!!!        call    data2LCD          ;   to position 1 of LCD
;!!! ENDIF
;******************************************************************************
;; Test if VFO A or B and put letter 'A' or 'B' in postion 1
;	movlw    'A'	; Guess that VFO A is active
;        	btfsc    vfo_select,VFO_SEL ; Determine active VFO
;        	movlw    'B'	; If it's B, then display B
;	call    data2LCD        ; to position 1 of LCD
;	movlw    ' '	; space to next position
;	call    data2LCD        ;   
;


; 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   LOW_NYBBLE_MASK   ; Mask for lower nibble only       (0000XXXX)   
        addlw   ASCII_NUM_BASE    ; 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   LOW_NYBBLE_MASK   ; Mask for lower nibble only       (0000YYYY)
        addlw   ASCII_NUM_BASE    ; Add offset for ASCII char set    (0030YYYY)
        call    data2LCD          ; Send byte in W to LCD
 
;;!!! IF LCDCHAR == 16
;        movlw   ','               ; Get a comma
;        call    data2LCD          ; Send byte in W to LCD
;;!!! ENDIF

        swapf   BCD_2,w           ; Swap 100KHz BCD digit into lower nibble of W
        andlw   LOW_NYBBLE_MASK   ; Mask for lower nibble only       (0000XXXX)
        addlw   ASCII_NUM_BASE    ; 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   LOW_NYBBLE_MASK   ; Mask for lower nibble only       (0000YYYY)
        addlw   ASCII_NUM_BASE    ; 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   LOW_NYBBLE_MASK   ; Mask for lower nibble only       (0000XXXX)
        addlw   ASCII_NUM_BASE    ; Add offset for ASCII char set    (0030XXXX)
        call    data2LCD          ; Send byte in W to LCD

;;!!! IF LCDCHAR == 16
;        movlw   '.'               ; Get a period
;        call    data2LCD          ; Send byte in W to LCD

;!!! IF LCD16_LINEAR == 0             ; If using the AmQRP 16x1 LCD,
;!!!        movlw   PICEL_LONG_9      ;   point to LCD digit number nine
;!!!        call    cmnd2LCD          ; Send command byte in W to LCD
;!!! ENDIF
;!!! ENDIF

        movf    BCD_1,w           ; Put 100 Hz BCD digit into lower nibble of W
        andlw   LOW_NYBBLE_MASK   ; Mask for lower nibble only       (0000YYYY)
        addlw   ASCII_NUM_BASE    ; 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   LOW_NYBBLE_MASK   ; Mask for lower nibble only       (0000XXXX)
        addlw   ASCII_NUM_BASE    ; 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   LOW_NYBBLE_MASK   ; Mask for lower nibble only       (0000YYYY)
        addlw   ASCII_NUM_BASE    ; Add offset for ASCII char set    (0030YYYY)
        call    data2LCD          ; Send byte in W to LCD

;;!!! IF LCDCHAR == 16
;        movlw   khz_offset        ; Display "KHz" on LCD
;        call    display_message   ; Position 16 is blank
;!!! ENDIF

        call    set_cursor_pos	  ; Indicate the lowest digit position
                                  ; being modified. - W3CD 3-26-04
;!!!        call    set_leds          ; Establish led states
        return                    ; 


;
; *****************************************************************************
;
;    Function: show_band
;
;    Purpose:  Displays the band number of the active vfo on the LCD at the
;              current display position
;
;    Input:    vfo_select
;
;    Output:   Current band number is indicated on the LCD display
;
;    Revisions:
;              Ver 2.0: New function. 4-4-04 W3CD.
;
; *****************************************************************************

show_band

        clrf    BCD_0             ; Clear the conversion target
        clrf    BCD_1             ; variables

        movf    vfo_a_band,w      ; Guess that VFO A is active
        btfsc  vfo_select,VFO_SEL ; Determine active VFO
        movf    vfo_b_band,w      ; If it's B, then get the band index
                                  ;
        movwf   temp              ; Save band number in temp
        incf    temp,f            ; Increment the band number so that
                                  ; band number starts with 1
        movlw   TEN               ; Put a value of 10 into W
        subwf   temp,w            ; Subtract 10 from band. Result in W
        btfss   STATUS, C         ; If carry is clear, then band >= 10
        goto    band_ones         ; If so, then process just the 1's digit
                                  ; Else, band is greater than 10
        movwf   BCD_0             ; Put the difference in the 1's digit
        incf    BCD_1,f           ; Add 1 to the 10's digit
        goto    display_band      ; skip ahead

band_ones
        movf    temp,w            ; Get the band number, since it's less than 10
        movwf   BCD_0             ; Store it in BCD_0.
                                  ; Now display it.
display_band
        movf    BCD_1,w           ; Get the 10's digit
        addlw   ASCII_NUM_BASE    ; Add ASCII '0'
        call    data2LCD          ; Send to LCD
                                  ;
        movf    BCD_0,w           ; Get the 1's digit
        addlw   ASCII_NUM_BASE    ; Add ASCII digit base
        call    data2LCD          ; Send to LCD
;!!!        call    set_leds          ; Establish led states
        return                    ; Back to caller

;
; *****************************************************************************
;
;    Function: set_cursor_pos
;
;    Purpose:  Position cursor at the frequency digit position being updated.
;
;    Input:    The cur_pos variable holds the digit position.
;
;    Output:   None
;
;    Revisions:
;              Ver 2.0: Added function to handle cursor placement on the
;                       LCD. 3-26-04 W3CD.
;              Ver 2.01: Fixed AmQRP 16x1 LCD cursor addressing bug
;                        4/20/04 W3CD.
;
; *****************************************************************************

set_cursor_pos                     ; This function depends on the LCD
                                   ; display being used. If it's 8
                                   ; characters wide, then operation is
                                   ; straightforward. It gets slightly
                                   ; more involved for 16 AmQRP character LCDs.

;!!! IF LCDCHAR == 8
;!!!        movlw   CMD_MOV_CUR_DISP   ; Cursor position base command
;!!!        iorwf   cur_pos,w          ; OR in the cursor position
;!!! ELSE
;!!! IF LCD16_LINEAR == 1
        movlw   CMD_MOV_CUR_DISP   ; Cursor position base command
        iorwf   cur_pos,w          ; Or in the cursor postion
;!!! ELSE
;!!!        movlw   LCD_POS_9          ; Get 9th cursor position
;!!!        subwf   cur_pos,w          ; Determine where the cursor is
;!!!        btfsc   STATUS,C           ; Carry is clear if cur_pos < 9th position
;!!!        goto    set_cur_1          ; If cur_pos >= 8, then make some adustments
                                   ; Else, use cursor address as-is
;!!!        movlw   CMD_MOV_CUR_DISP   ; Get positioning command
;!!!        addwf   cur_pos,w          ; Include the cursor position
;!!!        goto    set_cur_2          ; Finish up

;!!! set_cur_1                          ; Arrive here with delta cursor pos in W
;!!!        addlw   PICEL_LONG_9       ; Add command & offset for 9th position
;!!! set_cur_2                          ; Continue with cursor movement

;!!! ENDIF
;!!! ENDIF
        call    cmnd2LCD           ; Move the cursor
;!!!        call    set_leds           ; Restore LED inidications
        return                     ;

;
; *****************************************************************************
;
;    Function: clear_lcd
;
;    Purpose:  Clears the LCD and homes the cursor.
;
;    Input:    None
;
;    Output:   None
;
;    Revisions:
;              Ver 2.0: Added function for convenience and to reduce code space
;                       4-4-04 W3CD.
;
; *****************************************************************************
clear_lcd

        movlw   CMD_CLEAR_LCD     ; Prepare to display a new message on LCD
        call    cmnd2LCD          ; by clearing the current one
        return


; *****************************************************************************
; *                                                                           *
; * Purpose:  Check if LCD is done with the last operation.                   *
; *           This subroutine polls the LCD busy flag to determine if         *
; *           previous operations are completed.                              *
; *                                                                           *
; *   Input:  None                                                            *
; *                                                                           *
; * On exit:  PORTB set as:  RB3          input                               P
; *                          all others   outputs                             P
; *****************************************************************************
; 
busy_check
        clrf    PORTB             ; Clear all outputs on PORTB
        bsf     STATUS,RP0        ; Switch to bank 1 for Tristate operation
        movlw   b'00001000'       ; Set RB3 input, others outputs
        movwf   TRISB             ;   via Tristate
        bcf     STATUS,RP0        ; Switch back to bank 0
        bcf     PORTB,LCD_rs      ; Set up LCD for Read Busy Flag (RS = 0) 
        bsf     PORTB,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     PORTB,LCD_e       ; Set E high
        movf    PORTB,w           ; Read PORTB into W
        movwf   LCD_read          ; Save W for later testing
        bcf     PORTB,LCD_e       ; Drop E again
        nop                       ; Wait a
        nop                       ;   while
        bsf     PORTB,LCD_e       ; Pulse E high (dummy read of lower nibble),
        nop                       ;   wait,
        bcf     PORTB,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,LCD_busy ; Busy Flag (RB3) in save byte clear?
        goto    LCD_is_busy       ; If not, it is busy so jump back
not_busy
        return                    ; 


; *****************************************************************************
; * Purpose:  Send Command or Data byte to the LCD                            *
; *           Entry point cmnd2LCD:  Send a Command to the LCD                *
; *           Entry Point data2LCD:  Send a Data byte to the LCD              *
; *                                                                           *
; *   Input:  W has the command or data byte to be sent to the LCD.           *
; *                                                                           *
; *  Output:  None                                                            *
; *****************************************************************************

cmnd2LCD   ; ****** Entry point ******
        movwf   LCD_char          ; Save byte to write to LCD
        clrf    rs_value          ; Remember to clear RS  (clear rs_value)   
        bcf     PORTB,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     PORTB,LCD_rs      ; Set RS for Data to LCD
write2LCD
        call    busy_check        ; Check to see if LCD is ready for new data
        clrf    PORTB             ; Clear all of Port B (inputs and outputs)
        bsf     STATUS,RP0        ; Switch to bank 1 for Tristate operation
        movlw   0x00              ; Set up to enable PORTB data pins
        movwf   TRISB             ; All pins (RB7..RB0) are back to outputs
        bcf     STATUS,RP0        ; Switch to bank 0
        bcf     PORTB,LCD_rw      ; Set LCD back to Write mode  (RW = 0)
        bcf     PORTB,LCD_rs      ; Guess RS should be clear              
        btfsc   rs_value,0        ; Should RS be clear?  (is bit 0 == 0?) 
        bsf     PORTB,LCD_rs      ; No, set RS                            
;
; Transfer Most Significant nibble  (XXXX portion of XXXXYYYY)
;

        movlw   HI_NYBBLE_MASK    ; Set up mask
        andwf   PORTB,f           ; Keep RB7..RB4 but clear old RB3..RB0          
        swapf   LCD_char,w        ; Put byte into W (reverse nibbles)
        andlw   LOW_NYBBLE_MASK   ; Mask to give 0000XXXX in W
        iorwf   PORTB,f           ; To RB3..RB0 with RB7..RB4 unchanged
        bsf     PORTB,LCD_e       ; Pulse the E line high,
        nop                       ;   wait, 
        bcf     PORTB,LCD_e       ;   and drop it again
;
; Transfer Least Significant nibble  (YYYY portion of XXXXYYYY)
;
        movlw   HI_NYBBLE_MASK    ; Set up mask
        andwf   PORTB,f           ; Clear old RB3..RB0
        movf    LCD_char,w        ; Move LS nibble of into W
        andlw   LOW_NYBBLE_MASK   ; Mask to give 0000YYYY in W
        iorwf   PORTB,f           ; To RB3..RB0 with RB7..RB4 unchanged
        bsf     PORTB,LCD_e       ; Pulse the E line high,
        nop                       ;   wait, 
        bcf     PORTB,LCD_e       ;   and drop it again
        return

; ****************************************************************************
;
;    Purpose: This routine will save the current frequency band in EEPROM.
;             This band will then be used as the initial frequency upon
;             start up. The routine is entered by pressing and holding both
;             PB_1 and PB_2 for more than 2 seconds while in band change
;             mode. The band of the current VFO in use is used.
;
;    Input:   vfo_select, vfo_a_band, vfo_b_band
;
;    Output:  none
;
;    Revisions:
;             Ver 2.0: New function. 4-4-04 W3CD
;
; ****************************************************************************

update_EEPROM_band
        movlw   LOW startup_band    ; Get startup address
        movwf   ee_addr             ;   and set up for start of EEPROM writes
                                    ; Determine which VFO is active
        movf    vfo_a_band,w        ; Guess at VFO A
        btfsc   vfo_select,VFO_SEL  ; Check the select bit
        movf    vfo_b_band,w        ; If it's B, then get the band value
                                    ; Band data in w
        call    write_EEPROM        ; Store it in EEProm
        return                      ; Done.

                                                                            
; ****************************************************************************
;
;    Function: update_EEPROM_freq, update_EEPROM_cal
;
;    Purpose: This routine saves the current VFO frequency in EEPROM, using
;             the current VFO's band. The calibrate function updates EEPROM
;             by entering at the second address.
;
;    Input:   vfo_select
;
;    Output:  none
;
;    Revisions:
;             Ver 2.0: New function. 4-4-04 W3CD
;
; ****************************************************************************

update_EEPROM_freq

        movlw   BAND_ADJUST         ; Offset to vfo_X_band
        addwf   vfo_pointer,w       ; Calc address of vfo_X_band
        movwf   FSR                 ; Prepare for indirect addressing
        movf    INDF,w              ; Get the band number
        movwf   temp                ; Save band in temp

        movlw   band_table          ; Get the base address of EEPROM band table
        addwf   temp,w              ; Add in 4*band to
        addwf   temp,w              ;  obtain the proper
        addwf   temp,w              ;    table offset
        addwf   temp,w              ;
        movwf   ee_addr             ; Store EEPROM table entry start address

        incf    FSR,f               ; Increment to point to vfo_X_0

update_EEPROM_cal                   ; Entry point for storing new ref_osc bytes
        movlw   FREQ_COUNT          ; Prepare loop to write contents
        movwf   count               ; of active VFO to EEPROM

update_EE_loop
        movf    INDF,w              ; Get vfo frequency byte
        call    write_EEPROM        ; Do the write thing
        incf    FSR,f               ; Bump vfo freq pointer
        incf    ee_addr,f           ; Increment the EEPROM address
        decfsz  count,f             ; Decrement byte count
        goto    update_EE_loop      ; Iterate while bytes remain to be written
                                    ; Fall through when done
        return                      ; Done!

; *****************************************************************************
;
;    Function: read_EEPROM_freq
;
;    Purpose:  Reads a 4-byte frequency value from EEPROM
;
;    Input:    FSR points to the LSB into which the frequency is copied
;              ee_addr is setup with the proper frequency table pointer
;
;    Output:   The 4-byte target is updated with EEPROM frequency
;
;    Revisions:
;              Ver 2.0: Created 4-7-04 by W3CD.
;              Ver 2.1: Updated for PIC16F628A 10-03-05 W3CD
;
; *****************************************************************************

read_EEPROM_freq

        movlw   FREQ_COUNT        ; Get the byte count
        movwf   count             ; into loop counter

read_EEPROM_loop
        call    read_EEPROM       ; Read a byte. Data returned in w
        movwf   INDF              ; Store new frequency byte
        incf    ee_addr,f         ; Increment EEPROM read address
        incf    FSR,f             ; Point to next target address
        decfsz  count,f           ; Decrement byte count
        goto    read_EEPROM_loop  ; Loop if more bytes remaining.
        return                    ; Back to caller


; *****************************************************************************
; *                                                                           *
; * Purpose:  Write the byte of data at EEDATA to the EEPROM at address       *
; *           EEADR.                                                          *
; *                                                                           *
; *   Input:  w contains the data to be written.                              *
; *           ee_addr contains the write address.                             *
; *                                                                           *
; *  Output:  The EEPROM value is updated.                                    *
; *                                                                           *
; *  Revisions: PEGEN 2.1: Updated for PIC16F628A 10-03-05 W3CD               *
; *                                                                           *
; *****************************************************************************

write_EEPROM
        bsf     STATUS,RP0        ; Switch to bank 1
        movwf   EEDATA            ; Store the write data
        movf    ee_addr,w         ; Get the write address
        movwf   EEADR             ; Prepare EE address

        bsf     EECON1,WREN       ; Set the EEPROM write enable bit
        movlw   0x55              ; Write 0x55 and 0xAA to EEPROM
        movwf   EECON2            ;   control register, as required
        movlw   0xAA              ;   for the write
        movwf   EECON2            ; 
        bsf     EECON1,WR         ; Set WR to initiate write
bit_check
        btfsc   EECON1,WR         ; Has the write completed?
        goto    bit_check         ; No, keep checking
        bcf     EECON1,WREN       ; Clear the EEPROM write enable bit
        bcf     STATUS,RP0        ; Switch to bank 0 
        return                    ; Return to the caller

; *****************************************************************************
; *                                                                           *
; * Purpose:  Read a byte of EEPROM data at address EEADR into EEDATA.        *
; *                                                                           *
; *   Input:  The address EEADR.                                              *
; *                                                                           *
; *  Output:  The value in EEDATA.                                            *
; *                                                                           *
; *  Revisions: PEGEN 2.1: Updated for PIC16F628A. 10-03-05 W3CD              *
; *                                                                           *
; *****************************************************************************

read_EEPROM
        bsf     STATUS,RP0        ; Switch to bank 1
        movf    ee_addr,w         ; Get read address
        movwf   EEADR             ; Copy to EE register
        bsf     EECON1, RD        ; Request the read
        movf    EEDATA,w          ; Get the data
        bcf     STATUS,RP0        ; Switch to bank 0
        return                    ; Return to the caller


; *****************************************************************************
;
;    Function: set_leds
;
;    Purpose:  Restores the static LED states on Port B after display updates
;              and DDS daughter card writes.
;
;    Input:    led_states
;
;    Output:   LEDs are restored to their static values
;
;    Revisions:
;             Ver 2.0 Added function 4-6-04 W3CD
;
; *****************************************************************************

;!!!set_leds

;!!!        movlw   LED_MASK          ; Get led mask
;!!!        andwf   PORTB,F           ; Apply it to Port B
;!!!        movf    led_states,w      ; Get the led states
;!!!        iorwf   PORTB,f           ; Apply them to Port B
;!!!        return                    ; Done


; *****************************************************************************
; *                                                                           *
; * Purpose:  Wait for a specified number of milliseconds.                    *
; *                                                                           *
; *           Entry point wait_a_sec:  Wait for 1 second                      P
; *           Entry point wait_256ms:  Wait for 256 msec                      P
; *           Entry point wait_128ms:  Wait for 128 msec                      *
; *           Entry point wait_64ms :  Wait for 64 msec                       *
; *           Entry point wait_32ms :  Wait for 32 msec                       *
; *           Entry point wait_16ms :  Wait for 16 msec                       *
; *           Entry point wait_8ms  :  Wait for 8 msec                        *
; *           Entry point wait_1ms                                            *
; *                                                                           *
; *   Input:  None                                                            *
; *                                                                           *
; *  Output:  None                                                            *
; *                                                                           *
; *****************************************************************************
;
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


;       
; *****************************************************************************
;
        END
