; ****************************************************************************
; * PICELgen - Signal Generator (VFO) with Direct Digital Synthesis          *
; *  Version 1.4                                                             *
; *  March 12, 2004                                                          *
; *                                                                          *
; ****************************************************************************
; Description:
; This is the control program for a DDS VFO built with an AD9850 DDS chip, a
; shaft encoder, two push button switches and an Liquid crystal display.      P
;
; Features:
; VARIABLE RATE TUNING based on the speed at which the encoder is turned. 
; Pressing a pushbutton switch (PIC-EL PB_1) will change the step size from   P
; 1Hz to 1kHz.                                                                P
;
; BAND MEMORIES a pushbutton switch (PIC-EL PB_2) allows the frequency to     P
; be cycled around the HF ham bands.
;
; CALIBRATE MODE is entered if a pushbutton switch (PIC-EL PB_1) is pressed   P
; during power-on. The display is set to 10 MHz and remains fixed,            P 
; 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 down
; while turning.  

; An external frequency counter on the DDS output is required to observe this
; adjustment. 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.
; 
; MOVE UP 1 MHz - Press and hold PIC-EL pushbutton PB_2 and then press and    P
; then press and release PIC-EL pushbutton PB_1.                              P
;
; MOVE DOWN 1 MHz - Press and hold PIC-EL pushbutton PB_1 and then press and  P
; then press and release PIC-EL pushbutton PB_2.                              P
;
; SAVE NEW START-UP FREQUENCY - Select the frequency. Then press and hold     P
; PIC-EL pushbuttons PB_1 and PB_2 for 2 seconds.  The frequency will be      P
; stored in EEPROM and will be used on next start-up.                         P
;
;******************************************************************************
; 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, AAED (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
;                                                                             P
;*****************************************************************************
;                                                                             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--------XTAL                       P
;     +5V-----------!MCLR |4       15| OSC2--------XTAL                       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       PIC16F84
        radix           dec
;
; ****************************************************************************
; * Configuration fuse information:                                          *
; ****************************************************************************

_CP_ON                       EQU     H'000F'
_CP_OFF                      EQU     H'3FFF'
_PWRTE_ON                    EQU     H'3FF7'
_PWRTE_OFF                   EQU     H'3FFF'
_WDT_ON                      EQU     H'3FFF'
_WDT_OFF                     EQU     H'3FFB'
_LP_OSC                      EQU     H'3FFC'
_XT_OSC                      EQU     H'3FFD'
_HS_OSC                      EQU     H'3FFE'
_RC_OSC                      EQU     H'3FFF'
;
        __config        _CP_OFF & _PWRTE_ON & _WDT_OFF & _XT_OSC            ;P

; NOTE: Can select either 16-char or 8-char LCD by enabling one of these     P
;#DEFINE LCDCHAR 16 ; Turn on code for 16-character LCD                      P
#DEFINE LCDCHAR 8  ; Turn on code for 8-character LCD                       P
;
; ***************************************************************************P
; Info for power-up display                                                  P
MCODE_REV_0  equ  '1'     ; Current code version is 1.4                      P
MCODE_REV_1  equ  '.'     ;                                                  P
MCODE_REV_2  equ  '4'     ;                                                  P
MCODE_REV_3  equ  ' '     ;                                                  P
;                                                                            P
; ****************************************************************************
; * General equates.  These may be changed to accommodate the reference clock* 
; * frequency, the desired upper frequency limit, and the default startup    *
; * frequency.                                                               *
; ****************************************************************************
;
; ref_osc represents the change in the frequency control word which results 
; in a 1 Hz change in output frequency.  It is interpreted as a fixed point
; integer in the format <ref_osc_3>.<ref_osc_2><ref_osc_1><ref_osc_0>
;
; The values for common oscillator frequencies are as follows:
;
; Frequency    ref_osc_3    ref_osc_2    ref_osc_1    ref_osc_0
;
; 120.00 MHz     0x23         0xCA         0x98         0xCE                  
; 100.00 MHz     0x2A         0xF3         0x1D         0xC4                  
;  90.70 MHz     0x2F         0x5A         0x82         0x7A                  
;  66.66 MHz     0x40         0x6E         0x52         0xE7                  
;  66.00 MHz     0x41         0x13         0x44         0x5F                  
;  50.00 MHz     0x55         0xE6         0x3B         0x88                  
;
; To calculate other values: 
;    ref_osc_3 = (2^32 / oscillator_freq_in_Hertz).                           
;    ref_osc_2, ref_osc_1, and ref_osc_0 are the fractional part of           
;     (2^32 / oscillator_freq_in_Hertz) times 2^24.                           
;    Note:   2^32 = 4294967296 and 2^24 = 16777216
;
; For example, for a 120 MHz clock:
;    ref_osc_3 is (2^32 / 120 x 10^6) = 35.791394133 truncated to 35 (0x23)  
;    ref_osc_2 is the high byte of (.791394133 x 2^24) = 13277390.32         
;      13277390.32 = 0xCA98CE, so high byte is CA.                           
;    ref_osc_1 is the next byte of 0xCA98CE, or 98
;    ref_osc_0 is the last byte of 0xCA98CE, or CE
;
;==== Currently set for 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
;
; Limit contains the upper limit frequency 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
;
; Default contains the default startup frequency as a 32 bit integer.
;
default_3 equ 0x00                ; Most significant byte for 14.025 MHz
default_2 equ 0xD6                ; Next byte
default_1 equ 0x01                ; Next byte
default_0 equ 0x28                ; Least significant byte 
;
band_end equ    0x28              ; The offset to the last band table entry 
;
; PB_flags bits                                                              P
PB1first equ 0                    ; Bit set indicates PB1 pressed first      P
;
EEstartup_adr equ 4               ; Location of startup frequency in EEPROM  P
;
; ****************************************************************************
; *                    Port and EEPROM Constants                             *
; ****************************************************************************
;
PortA   equ     0x05
PortB   equ     0x06
TRISA   equ     0x05
TRISB   equ     0x06
EEdata  equ     0x08
EEadr   equ     0x09
WREN    equ     0x02
WR      equ     0x01
RD      equ     0x00
;
; ****************************************************************************
; * ID location information:                                                 *
; * (MPASM warns about DW here, don't worry)                                 *
; ****************************************************************************
;
        ORG     0x2000
        DATA    0x007F
        DATA    0x007F
        DATA    0x007F
        DATA    0x007F
;
;
; ****************************************************************************
; * Setup the initial constant, based on the frequency of the reference      *
; * oscillator.  This can be tweaked with the calibrate function.            *
; ****************************************************************************
; 
        ORG     0x2100
; ref_osc bytes must be first 4 bytes of EEPROM                              P
        DATA    ref_osc_0
        DATA    ref_osc_1
        DATA    ref_osc_2
        DATA    ref_osc_3
; startup frequency bytes must be next 4 bytes of EEPROM                     P
        DATA    default_0  ; startup -> freq_0                               P
        DATA    default_1  ; startup -> freq_1                               P
        DATA    default_2  ; startup -> freq_2                               P
        DATA    default_3  ; startup -> freq_3                               P
;
;       Clear unused EEPROM bytes.
;
        DATA    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
        DATA    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0         ;P
; 
; ****************************************************************************
; * RAM page independent file registers:                                     *
; ****************************************************************************
;
INDF    EQU     0x00
PCL     EQU     0x02
STATUS  EQU     0x03
FSR     EQU     0x04
PCLATH  EQU     0x0A
INTCON  EQU     0x0B
;
; *****************************************************************************
; * Bit numbers for the STATUS file register:                                 *
; *****************************************************************************
;
B_RP0   EQU     5
B_NTO   EQU     4
B_NPD   EQU     3
B_Z     EQU     2
B_DC    EQU     1
B_C     EQU     0
;
; ****************************************************************************
; * 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
;
; ****************************************************************************
; *           Allocate variables in general purpose register space           *
; ****************************************************************************
;
        CBLOCK  0x0c              ; Start Data Block
;
        freq_0                    ; Display frequency (hex) 
          freq_1                  ;  (4 bytes) 
          freq_2
          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
          fstep_3
        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        
          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_timer_0               ; For variable rate tuning 
          ren_timer_1             ;  (2 bytes)
        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 switch pin
        last_dir                  ; Indicates last direction of encoder
        next_dir                  ; Indicates expected direction
        count                     ; loop counter  (gets reused)
        band                      ; Used to index a table of frequencies
        rs_value                  ; The LCD rs line flag value
        PB_flags                  ; Pushbutton flags                         P
        PB_wait_count             ; Wait for 2 seconds                       P
;
        ENDC                      ; End of Data Block
; 
; ****************************************************************************
; * 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
;
; ****************************************************************************  
; * This is the band table.  Each entry is four instructions long, with each *
; * group of four literals representing the frequency as a 32 bit integer.   *
; * New entries can be added to the end of the table or between existing     *
; * entries.  The constant band_end must be incremented by 4 for each entry  *
; * added.                                                                   *
; *                                                                          *
; * This table is placed near the top of the program to allow as large a     *
; * a table as possible to be indexed with the eight bit value in W.         *
; *                                                                          *
; ****************************************************************************
;
band_table
        addwf   PCL,f             ; 
        retlw   0x00              ; 0 Hz
        retlw   0x00              ; 
        retlw   0x00              ;
        retlw   0x00              ;
        retlw   0x00              ; 160 meters
        retlw   0x1B              ; 
        retlw   0x77              ; 
        retlw   0x40              ; 
        retlw   0x00              ; 80 meters
        retlw   0x35              ; 
        retlw   0x67              ; 
        retlw   0xE0              ; 
        retlw   0x00              ; 40 meters
        retlw   0x6A              ; 
        retlw   0xCF              ; 
        retlw   0xC0              ; 
        retlw   0x00              ; 30 meters
        retlw   0x9A              ; 
        retlw   0x1D              ; 
        retlw   0x20              ; 
        retlw   0x00              ; 20 meters
        retlw   0xD5              ; 
        retlw   0x9F              ; 
        retlw   0x80              ; 
        retlw   0x01              ; 17 meters
        retlw   0x13              ; 
        retlw   0xB2              ;
        retlw   0x20              ; 
        retlw   0x01              ; 15 meters
        retlw   0x40              ; 
        retlw   0x6F              ; 
        retlw   0x40              ; 
        retlw   0x01              ; 12 meters
        retlw   0x7B              ; 
        retlw   0xCA              ; 
        retlw   0x90              ; 
        retlw   0x01              ; 10 meters
        retlw   0xAB              ; 
        retlw   0x3F              ; 
        retlw   0x00              ;
        retlw   0x01              ; 30 MHz
        retlw   0xC9              ;
        retlw   0xC3              ;
        retlw   0x80              ; 
;
; *****************************************************************************
; *                                                                           *
; * 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
        bsf     STATUS,B_RP0      ; Switch to bank 1
        bcf     0x01,7            ; Enable weak pullups                       P
        movlw   0xFB              ; Tristate PortA (all Inputs except RA2)    P
        movwf   TRISA             ;
        bcf     PortA,speaker     ; Set speaker output low (don't use here)   P
        clrf    TRISB             ; Set port B to all outputs
        bcf     STATUS,B_RP0      ; Switch back to bank 0
        call    init_LCD          ; Initialize the LCD
        call    display_version   ; Display title and version                 P
;
;       Enter Calibrate Mode if push button is pressed while turning the 
;       power on.
;
        btfsc   PortA,pb_1        ; Tuning-increment/Cal pushbutton pressed?  P
        goto    read_EEocs        ; No, get clock freq from EEPROM
        call    calibrate         ; Yes, calibrate
;
;       Get the reference oscillator constant from the EEPROM.
;
read_EEocs
        clrf    EEadr             ; Reset the EEPROM read address
        call    read_EEPROM       ; Read EEPROM
        movf    EEdata,w          ; Get the first osc byte
        movwf   osc_0             ; Save osc frequency
        call    read_EEPROM       ; Get next byte
        movf    EEdata,w          ; 
        movwf   osc_1             ; Save it
        call    read_EEPROM       ; Get the third byte
        movf    EEdata,w          ; 
        movwf   osc_2             ; Save it
        call    read_EEPROM       ; Get the fourth byte
        movf    EEdata,w          ;
        movwf   osc_3             ; Save it
;
;       Set the power on frequency to the defined value.
;       (They always follow the osc bytes)                                    P
;
        call    read_EEPROM       ; Read EEPROM                               P
        movf    EEdata,w          ; Get the first default freq byte           P
        movwf   freq_0            ; Save it
        call    read_EEPROM       ; Read EEPROM                               P
        movf    EEdata,w          ; Get the next freq byte                    P
        movwf   freq_1            ; Save it
        call    read_EEPROM       ; Read EEPROM                               P
        movf    EEdata,w          ; Get the next freq byte                    P
        movwf   freq_2            ; Save it                                    
        call    read_EEPROM       ; Read EEPROM                               P 
        movf    EEdata,w          ; Get the last freq byte                    P
        movwf   freq_3            ; Save it
;
;       Display the power on frequency.
;
        call    bin2BCD           ; Convert it to BCD
        call    show_freq         ; Display it
;
;       Send power on frequency to the DDS chip.
;
        call    calc_dds_word     ; Convert to delta value
        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       P
                                  ;   a clean start-up                        P 
;
;       Get the power on encoder value.
;
        movf    PortA,w           ; Read port A
        movwf   ren_read          ; Save it in ren_read
        movlw   0x03              ; Get encoder mask (RA0 and RA1)  
        andwf   ren_read,w        ; Get encoder bits
        movwf   ren_old           ; Save in ren_old
;
;       Initialize variables.
;
        clrf    ren_timer_1       ; Initialize the encoder speed timer        
        movlw   0x40              ;   to                                     
        movwf   ren_timer_0       ;     0x0040                               
        clrf    last_dir          ; Clear the knob direction indicator
        clrf    band              ; Clear the band indicator
; 
; 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!)     P
                                  ; Return here when encoder change detected  P
        btfsc   ren_read,pb_2     ; Is bandswitch pushbutton pressed?         P
        goto    step              ; No, just step                             P
        call    change_band       ; Yes, change_band                          P
        goto    main              ; Continue main loop                        P
step    ;                                                                     P
;
;       Determine step size to use (1 Hz or 1 kHz).
;
        clrf    fstep_3           ; Guess that we want 1 Hz steps by 
        clrf    fstep_2           ;   setting fstep to one.
        clrf    fstep_1           ; 
        movlw   0x01              ;
        movwf   fstep_0           ; 
        btfsc   ren_read,pb_1     ; Is the tuning-increment button pressed?   P
        goto    go_step           ; No, use the 1 Hz step
        movlw   0xE8              ; Yes, set the step value to 1 kHz
        movwf   fstep_0           ;   by setting fstep_0 to 0xE8 and
        movlw   0x03              ;   fstep_1 to 0x03
        movwf   fstep_1           ; 
        goto    go_step           ; Use the 1 kHz step
;
;       Adjust the tuning step based on ren_timer.  ren_timer is incremented
;       by 8 from its initial value of 0x0040 each time the poll_encoder finds 
;       no change in the encoder input, until the high bit of ren_timer_1
;       becomes a one.  The default fstep of 1 Hz is multiplied by two for
;       each leading zero in ren_timer, up to a maximum of 9 times. (This is
;       because ren_timer starts at 0x0040, only the first nine bits can be 
;       zero in a row).  The faster the knob is turned, the lower the number 
;       in ren_timer will be, and the larger the step value will be.
;   
;
bump_step
        bcf     STATUS,B_C        ; Clear the carry flag
        rlf     fstep_0,f         ; Multiply the step by 2 by rotating left
        rlf     fstep_1,f         ; 
        rlf     fstep_2,f         ; 
        rlf     fstep_3,f         ; 
go_step
        rlf     ren_timer_0,f     ; Multiply the encoder timer by 2
        rlf     ren_timer_1,f     ;
        btfss   STATUS,B_C        ; Has a one floated to the carry yet?
        goto    bump_step         ; No, then double the step size
;
;       Based on the knob direction, either add or subtract the increment, 
;       then update the LCD and DDS.
; 
        btfsc   last_dir,1        ; Is the knob going up?
        goto    up                ; Yes, then add the increment
down
        call    sub_step          ; Subtract fstep from freq
        goto    update            ; Update LCD and DDS                        P
up
        call    add_step          ; Add fstep to freq
        call    check_add         ; Make sure we did not exceed the maximum
update 
        call    bin2BCD           ; Convert the frequency to BCD
        call    show_freq         ; Display the frequency on the LCD
        call    calc_dds_word     ; Find the control word for the DDS chip
        call    send_dds_word     ; Send the control word to the DDS chip
        goto    main              ; Continue main loop                        P
;
; *****************************************************************************
; *                                                                           *
; * Purpose:  This routine increments through the band table each time the    *
; *           knob moves a notch, 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.       *
; *                                                                           *
; *****************************************************************************
;
change_band
        btfsc   last_dir,1        ; Are we going up in the band list? 
        goto    band_up           ; Yes, increment band address
        movlw   0x04              ; No, get 4 bytes to subtract
        subwf   band,f            ; Move down in band list
        movlw   0xFF-band_end     ; Check to see if we have fallen off the
        addwf   band,w            ;   bottom of the table.
        btfss   STATUS,B_C        ; Off the bottom?
        goto    valid             ; No, continue
        movlw   band_end          ; Yes, go to highest entry
        movwf   band              ; 
valid
        call    get_band          ; Get the new band frequency
        goto    write             ; Set the frequency and continue
band_up
        movlw   0x04              ; Table entries are 4 bytes apart
        addwf   band,f            ; Increment the band pointer
        movlw   0xFF-band_end     ; Check to see if we have gone over the
        addwf   band,w            ;   top of the table.
        btfsc   STATUS,B_C        ; Did we go over the top of the table?
        clrf    band              ; Yes, go to the bottom entry
        call    get_band          ; Get the new band frequency
write
        call    bin2BCD           ; Convert the frequency to BCD
        call    show_freq         ; Display the frequency on the LCD
        call    calc_dds_word     ; Find the control word for the DDS chip
        call    send_dds_word     ; Send the control word to the DDS chip
        return                    ; Return to main loop                       P
;
; *****************************************************************************
; *                                                                           *
; * Purpose:  This routine reads the frequency value of a band table entry    *
; *           pointed to by band and returns it in freq_3...freq_0.           *
; *                                                                           *
; *   Input:  band must contain the index of the desired band entry * 4       *
; *           (with the entries numbered from zero).                          *
; *                                                                           *
; *  Output:  The band frequency in freq.                                     *
; *                                                                           *
; *****************************************************************************
;
get_band
        movf    band,w            ; Get the index of the high byte 
        call    band_table        ; Get the value into W
        movwf   freq_3            ; Save it in freq_3
        incf    band,f            ; Increment index to next byte
        movf    band,w            ; Get the index of the next byte
        call    band_table        ; Get the value into W
        movwf   freq_2            ; Save it in freq_2
        incf    band,f            ; Increment index to the next byte
        movf    band,w            ; Get the index to the next byte
        call    band_table        ; Get the value into W
        movwf   freq_1            ; Save it in freq_1
        incf    band,f            ; Increment index to the low byte
        movf    band,w            ; Get the index to the low byte
        call    band_table        ; Get the value into W
        movwf   freq_0            ; Save it in freq_0
        movlw   0x03              ; Get a constant three
        subwf   band,f            ; Restore original value of band
        return                    ; Return to the caller
;
; *****************************************************************************
; *                                                                           *
; * 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
;OLD    movlw   0x30              ; LCD init instruction (First)
;        Put 4-bit command in RB3..RB0                                        P
;        PIC's RB3..RB0 lines connect to LCD's DB7..DB4 (pins 14-11)          P 
        movlw   0x03              ; LCD init instruction (First)              P
        movwf   PortB             ; Send to LCD via RB3..RB0                  P
        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 
;OLD    movlw   0x30              ; LCD init instruction (Second)
        movlw   0x03              ; LCD init instruction (Second)             P
        movwf   PortB             ; Send to LCD via RB3..RB0                  P
        bsf     PortB,LCD_e       ; Set E high,
        call    wait_32ms         ;   wait a while,
        bcf     PortB,LCD_e       ;   and then Clear E 
;OLD    movlw   0x30              ; LCD init instruction (Third)
        movlw   0x03              ; LCD init instruction (Third)              P
        movwf   PortB             ; Send to LCD via RB3..RB0                  P
        bsf     PortB,LCD_e       ; Set E high,
        call    wait_32ms         ;   wait a while,
        bcf     PortB,LCD_e       ;   and then Clear E
;OLD    movlw   0x20              ; 4-bit mode instruction
        movlw   0x02              ; 4-bit mode instruction                    P
        movwf   PortB             ; Send to LCD via RB3..RB0                  P
        bsf     PortB,LCD_e       ; Set E high,
        call    wait_16ms         ;   wait a while,
        bcf     PortB,LCD_e       ;   and then Clear E
        movlw   0x28              ; 1/16 duty cycle, 5x8 matrix
        call    cmnd2LCD          ; Send command in w to LCD
        movlw   0x08              ; Display off, cursor and blink off 
        call    cmnd2LCD          ; Send command to LCD
        movlw   0x01              ; Clear and reset cursor
        call    cmnd2LCD          ; Send command in w to LCD
        movlw   0x06              ; Set cursor to move right, no shift
        call    cmnd2LCD          ; Send command in w to LCD
        movlw   0x0C              ; Display on, cursor and blink off
        call    cmnd2LCD          ; Send command in w to LCD
        return                    ; 
;
; ****************************************************************************P
; *                                                                           P
; * Purpose:  Display version and other info on LCD for 2 seconds             P
; *           upon power-up                                                   P
; *                                                                           P
; *   Input:  MCODE_REV_0 through MCODE_REV_4 set up                          P
; *                                                                           P
; *  Output:  LCD displays debug info                                         P
; *                                                                           P
; ****************************************************************************P
;
display_version
#IF LCDCHAR == 8
        movlw   0x80              ; Point LCD at digit 1                
        call    cmnd2LCD          ;
        movlw   'P'               ; digit 1              
        call    data2LCD          ;
        movlw   'I'               ; digit 2                  
        call    data2LCD          ;
        movlw   'C'               ; digit 3                
        call    data2LCD          ;
        movlw   'E'               ; digit 4               
        call    data2LCD          ;
        movlw   'L'               ; digit 5              
        call    data2LCD          ;
        movlw   'g'               ; digit 6              
        call    data2LCD          ;
        movlw   'e'               ; digit 7               
        call    data2LCD          ;
        movlw   'n'               ; digit 8               
        call    data2LCD          ;
        call    wait_a_sec        ; Wait one second
;
        movlw   0x80              ; Point LCD at digit 1              
        call    cmnd2LCD          ;
        movlw   'V'               ; digit 1              
        call    data2LCD          ;
        movlw   'e'               ; digit 2              
        call    data2LCD          ;
        movlw   'r'               ; digit 3                
        call    data2LCD          ;
        movlw   ' '               ; digit 4                   
        call    data2LCD          ;
        movlw   MCODE_REV_0       ; Get mcode rev byte
        call    data2LCD          ;   and display it (digit 5)
        movlw   MCODE_REV_1       ; Get mcode rev byte
        call    data2LCD          ;   and display it (digit 6)
        movlw   MCODE_REV_2       ; Get mcode rev byte
        call    data2LCD          ;   and display it (digit 7)
        movlw   MCODE_REV_3       ; Get mcode rev byte
        call    data2LCD          ;   and display it (digit 8)
        call    wait_a_sec        ; Wait one second
#ENDIF ; End of 8-character LCD code
#IF LCDCHAR == 16 ; Start 16-character LCD code
        movlw   0x80              ; Point LCD at digit 1                
        call    cmnd2LCD          ;
        movlw   'P'               ; digit 1              
        call    data2LCD          ;
        movlw   'I'               ; digit 2                  
        call    data2LCD          ;
        movlw   'C'               ; digit 3                
        call    data2LCD          ;
        movlw   'E'               ; digit 4               
        call    data2LCD          ;
        movlw   'L'               ; digit 5              
        call    data2LCD          ;        
        movlw   'g'               ; digit 6              
        call    data2LCD          ;
        movlw   'e'               ; digit 7               
        call    data2LCD          ;
        movlw   'n'               ; digit 8               
        call    data2LCD          ;
        movlw   0xC0              ; Point LCD at digit 9
        movwf   LCD_char          ; 
        call    cmnd2LCD          ; Send command to LCD
        movlw   ' '               ; Space in digit 9
        call    data2LCD          ;
        movlw   'V'               ; digit 10             
        call    data2LCD          ;
        movlw   'e'               ; digit 11              
        call    data2LCD          ;
        movlw   'r'               ; digit 12                
        call    data2LCD          ;
        movlw   MCODE_REV_0       ; Get mcode rev byte
        call    data2LCD          ;   and display it (digit 13)
        movlw   MCODE_REV_1       ; Get mcode rev byte
        call    data2LCD          ;   and display it (digit 14)
        movlw   MCODE_REV_2       ; Get mcode rev byte
        call    data2LCD          ;   and display it (digit 15)
        movlw   MCODE_REV_3       ; Get mcode rev byte
        call    data2LCD          ;   and display it (digit 16)
        call    wait_a_sec        ; Wait one second
#ENDIF ; End of 16-character LCD
; 
        return

;
; *****************************************************************************
; *                                                                           *
; * 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.                                                    *
; *                                                                           *
; *****************************************************************************
add_step
        movf    fstep_0,w         ; Get low byte of the increment
        addwf   freq_0,f          ; Add it to the low byte of freq
        btfss   STATUS,B_C        ; Any carry?
        goto    add1              ; No, add next byte
        incfsz  freq_1,f          ; Ripple carry up to the next byte
        goto    add1              ; No new carry, add next byte
        incfsz  freq_2,f          ; Ripple carry up to the next byte
        goto    add1              ; No new carry, add next byte
        incf    freq_3,f          ; Ripple carry up to the highest byte
add1
        movf    fstep_1,w         ; Get the next increment byte
        addwf   freq_1,f          ; Add it to the next higher byte
        btfss   STATUS,B_C        ; Any carry?
        goto    add2              ; No, add next byte
        incfsz  freq_2,f          ; Ripple carry up to the next byte
        goto    add2              ; No new carry, add next byte
        incf    freq_3,f          ; Ripple carry up to the highest byte
add2
        movf    fstep_2,w         ; Get the next to most significant increment
        addwf   freq_2,f          ; Add it to the freq byte
        btfss   STATUS,B_C        ; Any carry?
        goto    add3              ; No, add last byte
        incf    freq_3,f          ; Ripple carry up to the highest byte
add3
        movf    fstep_3,w         ; Get the most significant increment byte
        addwf   freq_3,f          ; Add it to the most significant freq
        return                    ; Return to the caller
;
; *****************************************************************************
; *                                                                           *
; * Purpose:  Check if freq exceeds the upper limit.                          *
; *                                                                           *
; *   Input:  The 32 bit values in freq                                       *
; *                                                                           *
; *  Output:  If freq is below the limit, it is unchanged.  Otherwise, it is  *
; *           set to equal the upper limit.                                   *
; *                                                                           *
; *****************************************************************************
;
check_add
;
;       Check the most significant byte.
;
        movlw   0xFF-limit_3      ; Get (FF - limit of high byte)
        addwf   freq_3,w          ; Add it to the current high byte
        btfsc   STATUS,B_C        ; Was high byte too large?
        goto    set_max           ; Yes, apply limit
        movlw   limit_3           ; Get high limit value
        subwf   freq_3,w          ; Subtract the limit value
        btfss   STATUS,B_C        ; Are we at the limit for the byte?
        goto    exit1             ; No, below.  Checks are done.
;
;       Check the second most significant byte.
;
        movlw   0xFF-limit_2      ; Get (FF - limit of next byte)
        addwf   freq_2,w          ; Add it to the current byte
        btfsc   STATUS,B_C        ; Is the current value too high?
        goto    set_max           ; Yes, apply the limit
        movlw   limit_2           ; Second limit byte
        subwf   freq_2,w          ; Subtract limit value
        btfss   STATUS,B_C        ; Are we at the limit for the byte?
        goto    exit1             ; No, below.  Checks are done.
;
;       Check the third most significant byte.
; 
        movlw   0xFF-limit_1      ; Get (FF - limit of next byte)
        addwf   freq_1,w          ; Add it to the current byte
        btfsc   STATUS,B_C        ; Is the current value too high?
        goto    set_max           ; Yes, apply the limit
        movlw   limit_1           ; Third limit byte
        subwf   freq_1,w          ; Subtract limit value
        btfss   STATUS,B_C        ; Are we at the limit for the byte?
        goto    exit1             ; No, below.  Checks are done.
; 
;       Check the least significant byte.
;
        movlw   limit_0           ; Fourth limit byte
        subwf   freq_0,w          ; Subtract limit value
        btfss   STATUS,B_C        ; Are we at the limit for the byte?
        goto    exit1             ; No, below.  Checks are done.
set_max
        movlw   limit_0           ; Get least significant limit
        movwf   freq_0            ; Set it in freq
        movlw   limit_1           ; Get the next byte limit
        movwf   freq_1            ; Set it in freq_1
        movlw   limit_2           ; Get the next byte limit
        movwf   freq_2            ; Set it in freq_2
        movlw   limit_3           ; Get the most significant limit
        movwf   freq_3            ; Set it in freq_3
exit1
        return                    ; Return to the caller
;
; *****************************************************************************
; *                                                                           *
; * 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.                                      *
; *                                                                           *
; *****************************************************************************
;
sub_step
        comf    fstep_0,f         ; Subtraction of fstep from
        comf    fstep_1,f         ;   freq is done by adding the
        comf    fstep_2,f         ;   twos compliment of fstep to
        comf    fstep_3,f         ;   freq.
        incfsz  fstep_0,f         ; Increment last byte
        goto    comp_done         ; Non-zero, continue
        incfsz  fstep_1,f         ; Increment next byte
        goto    comp_done         ; Non-zero, continue
        incfsz  fstep_2,f         ; Increment next byte
        goto    comp_done         ; Non-zero, continue
        incf    fstep_3,f         ; Increment the high byte
comp_done
        call    add_step          ; Add the compliment to do the subtraction
;
;       If the frequency has gone negative, clear it to zero.
;
        btfss   freq_3,7          ; Is high order frequency byte "negative"?   
        goto    exit2             ; No, keep going
set_min
        clrf    freq_0            ; Yes, set the frequency to zero
        clrf    freq_1            ; 
        clrf    freq_2            ; 
        clrf    freq_3            ; 
exit2
        return                    ; Return to the caller
;
; *****************************************************************************
; *                                                                           *
; * Purpose:  This routine does the following:                                *
; *             1.  Records how long it took for the knob to move a notch     *
; *                 in ren_timer.                                             *
; *             2.  Clears the watchdog timer.                                *
; *             3.  Reads the encoder bits until a change is detected, then   *
; *                 determines the direction the knob was moved.              *
; *                                                                           *
; *   Input:  Knob input read from port A                                     *
; *           ren_old -> the last encoder bits read                           *
; *           last_dir -> the last direction moved                            *
; *                                                                           *
; *  Output:  ren_timer -> an indication the speed of the knob.               *
; *           ren_new -> the current encoder bits                             *
; *           last_dir -> the last direction (0 = down, 2 = up)               *
; *                                                                           *
; *****************************************************************************
;
poll_encoder
        clrf    ren_timer_1       ; Put starting values in ren_timer cells    P
        movlw   0x40              ; Start with the high bit set               P
        movwf   ren_timer_0       ;   in ren_timer_0                          P
read_encoder
        call    PB_look           ; Look at PB's for possible 1 MHz jump      P
        btfsc   ren_timer_1,7     ; Has the bit floated to top of ren_timer_1?P
        goto    no_inc            ; Yes, don't move it any further
        movlw   0x08              ; No, keep going                            P
        addwf   ren_timer_0,f     ; Add constant to ren_timer_0               P
        btfsc   STATUS,B_C        ; Did the add force a carry?
        incf    ren_timer_1,f     ; Yes, then add one to ren_timer_1
no_inc                            ; 
        movf    PortA,w           ; Get the current encoder value
        movwf   ren_read          ; Save it
        movlw   0x03              ; Get encoder mask (to isolate RA0 and RA1) P
        andwf   ren_read,w        ; Isolated encoder bits into W              P
        movwf   ren_new           ; Save new value
        xorwf   ren_old,w         ; Has it changed?
        btfsc   STATUS,B_Z        ; Check zero-flag (zero if no change)       P
        goto    read_encoder      ; No change, keep looking until it changes  P
;                                 ; Zero-flag is not set, so continue on      P 
; It changed. Now determine which direction the encoder turned.               P
;=============================================================================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,B_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   0x02              ; 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.
;
        btfsc   STATUS,B_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    read_encoder      ; Try again
pe_continue
        clrf    last_dir          ; Clear last_dir (default is DN)            P
        btfss   ren_old,1         ; Are we going UP?                          P
        goto    exit3             ; No, exit3                                 P
up2 
        movlw   0x02              ; Get UP value                              P
        movwf   last_dir          ;   and set in last_dir                     P
exit3 
        movf    ren_new,w         ; Get the current encoder bits
        movwf   ren_old           ; Save them in ren_old for the next time
        return                    ; Return to the caller
;
; ****************************************************************************P
; *                                                                           P
; * Purpose:  This routine is entered to see if the pushbuttons PB1 and PB2   P
; *           being pressed.                                                  P
; *           If both are being pressed, determine direction (depending on    P
; *             which was first) and change the frequency UP/DN by 1 MHz.     P
; *           If only one is being pressed, remember it as being "first".     P
; *                                                                           P
; *   Input:  None                                                            P
; *   Output: PB_flag set up                                                  P 
; ****************************************************************************P
;                                                                             P
PB_look ;                                                                     P
        btfss   PortA,pb_1        ; Is PB_1 being pressed?                    P
        goto    PB_yes1           ; Yes, PB_yes1                              P
PB_no1       ; 0,X                                                            P
        btfsc   PortA,pb_2        ; Not1, but is PB_2 being pressed?          P
        goto    PB_neither        ; No, Neither                               P
        goto    PB_2butnot1       ; Yes, PB_2butnot1                          P
PB_yes1     ; 1,X                                                             P
        btfsc   PortA,pb_2        ; Is PB_2 being pressed?                    P
        goto    PB_1butnot2       ; No, PB_1butnot2                           P
        ; fall through to PB_both ; Yes, PB_both                              P
PB_both     ; 1,2                                                             P
        ; both set, so add code to find out which was first.                  P
        btfsc   PB_flags,PB1first ; Was PB1 pressed first?                    P
        goto    PB_up             ; Yes, going down by 1 MHz                  P
        goto    PB_dn             ; NO, PB2 was first, so going UP by 1 MHz   P
PB_2butnot1 ; 0,2                                                             P
        bsf     PB_flags,PB1first ; Set PB1 first                             P
        goto    PB_exit           ; Exit                                      P
PB_1butnot2 ; 1,0                                                             P
        bcf     PB_flags,PB1first ; Clear PB1 first                           P
        goto    PB_exit           ; Exit                                      P
PB_neither  ; 0,0                                                             P
        clrf    PB_flags          ; Neither, so clear flags                   P
        goto    PB_exit           ; Exit                                      P
;                                                                             P
PB_up ;                                                                       P
        ; PB_2 was pressed first, then PB_1                                   P
        ; Wait for PB_1 to be released                                        P
        ; (If wait is longer than 2 seconds, call update_EEPROM and exit)     P
        movlw   0x10              ; Set up                                    P
        movwf   PB_wait_count     ;   loop count                              P
PB_up_release_wait ;                                                          P
        btfsc   PortA,pb_1        ; Is PB_1 still behing held?                P
        goto    PB_up_released    ; No, it's released, so move up             P
        call    wait_128ms        ; Wait a bit                                P
        decfsz  PB_wait_count,f   ; Have we waited long enough?               P
        goto    PB_up_release_wait ; No, continue waiting                     P
                                  ; Button was held longer than 2 seconds, so P
        call    update_EEPROM     ; Update EEPROM with current frequency      P
        goto    PB_exit           ;   and exit                                P
;                                                                             P
PB_up_released ;                                                              P
        clrf    fstep_3           ;                                           P
        movlw   0x0F              ; Setup for step of 1 M                     P
        movwf   fstep_2           ;                                           P
        movlw   0x42              ;                                           P
        movwf   fstep_1           ;                                           P
        movlw   0x40              ;                                           P
        movwf   fstep_0           ;                                           P
        call    add_step          ; Add a step and update <freq_0:freq_3>     P
        call    check_add         ; See if band limits reached. If so, adjust P 
                                  ;   and update <freq_0:freq_3>              P
        goto    PB_update         ;                                           P
;                                                                             P
PB_dn ;                                                                       P
        ; PB_1 was pressed first, then PB_2                                   P
        ; Wait for PB_2 to be released                                        P
        ; (If wait is longer than 2 seconds, call update_EEPROM and exit)     P
        movlw   0x10              ; Set up                                    P
        movwf   PB_wait_count     ;   loop count                              P
PB_dn_release_wait ;                                                          P
        btfsc   PortA,pb_2        ; Is PB_2 still being held?                 P
        goto    PB_dn_released    ; No, it's released, so move up             P
        call    wait_128ms        ; Wait a bit                                P
        decfsz  PB_wait_count,f   ; Have we waited long enough?               P
        goto    PB_dn_release_wait ; No, continue waiting                     P
                                  ; Button was held longer than 2 seconds, so P
        call    update_EEPROM     ; Update EEPROM with current frequency      P
        goto    PB_exit           ;   and exit                                P
;                                                                             P
PB_dn_released ;                                                              P
        clrf    fstep_3           ;                                           P
        movlw   0x0F              ; Setup for step of 1 M                     P
        movwf   fstep_2           ;                                           P
        movlw   0x42              ;                                           P
        movwf   fstep_1           ;                                           P
        movlw   0x40              ;                                           P
        movwf   fstep_0           ;                                           P
        call    sub_step          ; Add a step and update <freq_0:freq_3>     P
;                                                                             P
PB_update ;                                                                   P
        call    bin2BCD           ; Convert the frequency to BCD              P
        call    show_freq         ; Display the frequency on the LCD          P
        call    calc_dds_word     ; Find the control word for the DDS chip    P
        call    send_dds_word     ; Send the control word to the DDS chip     P
;                                                                             P
PB_exit ;                                                                     P
        return  ;                                                             P
;                                                                             P
; *****************************************************************************
; *                                                                           *
; * Purpose:  This routine is entered at start up if the calibrate            P
; *              push button is (PIC-EL PB_1) is pressed at power-on time.    P
; *                                                                           *
; *           If a 8-character LCD is used,"10000000" is displayed on the LCD P
; *           If a 16-character LCD is used," 10,000.000 CAL " is displayed   P
; *             on the LCD.                                                   P
; *                                                                           *
; *           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                            *
; *                                                                           *
; *****************************************************************************
;
calibrate
        movlw   0x80              ; Set frequency to 10MHz by setting freq
        movwf   freq_0            ;   to the binary equivalent of 10 MHz
        movlw   0x96              ;    
        movwf   freq_1            ;    
        movlw   0x98              ;    
        movwf   freq_2            ;   
        movlw   0x00              ;   
        movwf   freq_3            ;   
;
; Read the starting reference oscillator value form EEPROM.
;
        clrf    EEadr             ; Reset the EEPROM read address
        call    read_EEPROM       ; Read EEPROM
        movf    EEdata,w          ; Get the first osc byte
        movwf   osc_0             ; Save osc frequency
        call    read_EEPROM       ; Get next byte
        movf    EEdata,w          ; 
        movwf   osc_1             ; Save it
        call    read_EEPROM       ; Get the third byte
        movf    EEdata,w          ; 
        movwf   osc_2             ; Save it
        call    read_EEPROM       ; Get the fourth byte
        movf    EEdata,w          ;
        movwf   osc_3             ; Save it
        call    bin2BCD           ; Calculate BCD version of 10 MHz
        call    show_freq         ; Display the frequency on the LCD

#IF LCDCHAR == 16
        movlw   0xC4              ; Point LCD at position 13
        movwf   LCD_char          ; 
        call    cmnd2LCD          ; Send command to LCD
        movlw   'C'               ; Send a C (position 13)
        movwf   LCD_char          ; 
        call    data2LCD          ; 
        movlw   'A'               ; Send an A (position 14)
        movwf   LCD_char          ; 
        call    data2LCD          ; 
        movlw   'L'               ; Send an L (position 15)
        movwf   LCD_char          ; 
        call    data2LCD          ; 
        ;                         ; (position 16 is blank)
#ENDIF

cal_loop
        call    calc_dds_word     ; Calculate DDS value based on current osc
        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   0x10              ; Assume that we are adjusting slowly
        movwf   fstep_0           ; Use small increment
        btfsc   ren_read,2        ; Was the encoder changing slowly?
        goto    update_osc        ; Yes, then continue with small increment
        movlw   0x80              ; No, then use the large increment
        movwf   fstep_0           ;
update_osc
        nop                       ; Wait a cycle
        btfsc   last_dir,1        ; Are we moving down?
        goto    faster            ; No, increase the osc value
;
;       slower
;
        comf    fstep_0,f         ; Subtraction of fstep is done by
        comf    fstep_1,f         ;   adding the twos compliment of fsetp
        comf    fstep_2,f         ;   to osc
        comf    fstep_3,f         ; 
        incfsz  fstep_0,f         ; Increment last byte
        goto    faster            ; Non-zero, continue
        incfsz  fstep_1,f         ; Increment next byte
        goto    faster            ; Non-zero, continue
        incfsz  fstep_2,f         ; Increment next byte
        goto    faster            ; Non-zero, continue
        incf    fstep_3,f         ; Increment the high byte
faster
        movf    fstep_0,w         ; Get the low byte increment
        addwf   osc_0,f           ; Add it to the low osc byte
        btfss   STATUS,B_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,B_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,B_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?      P
        goto    cal_loop          ; Yes, stay in calibrate mode
;
        clrf    EEadr             ; Write final value to EEPROM 
        movf    osc_0,w           ; Record the first
        movwf   EEdata            ;   osc
        call    write_EEPROM      ;   byte
        movf    osc_1,w           ; Record the second
        movwf   EEdata            ;   osc
        call    write_EEPROM      ;   byte
        movf    osc_2,w           ; Record the third
        movwf   EEdata            ;   osc
        call    write_EEPROM      ;   byte
        movf    osc_3,w           ; Record the fourth
        movwf   EEdata            ;   osc
        call    write_EEPROM      ;   byte
        return                    ; Return to the caller

;                                                                             
; ****************************************************************************P
; *                                                                           P
; * Purpose:  This routine will save the current frequency in EEPROM. This    P
; *           frequency will then be used as the initial frequency upon start P
; *           up.  The routine is entered by pressing and holding both PB_1   P
; *           and PB_2 for more than 2 seconds.         .                     P
; *                                                                           P
; *   Input:  The original osc constant in EEPROM                             P
; *                                                                           P
; *  Output:  The corrected osc constant in EEPROM                            P
; *                                                                           P
; ****************************************************************************P
;                                                                             P
update_EEPROM ;                                                               P
        movlw   EEstartup_adr     ; Get startup address                       P
        movwf   EEadr             ;   and set up for start of EEPROM writes   P
        movf    freq_0,w          ; Record the first                          P
        movwf   EEdata            ;   freq                                    P
        call    write_EEPROM      ;   byte                                    P
        movf    freq_1,w          ; Record the second                         P
        movwf   EEdata            ;   osc                                     P
        call    write_EEPROM      ;   byte                                    P
        movf    freq_2,w          ; Record the third                          P
        movwf   EEdata            ;   osc                                     P
        call    write_EEPROM      ;   byte                                    P
        movf    freq_3,w          ; Record the fourth                         P
        movwf   EEdata            ;   osc                                     P
        call    write_EEPROM      ;   byte                                    P
        return                    ;                                           P
;                                                                             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        ; 
mult_loop
        bcf     STATUS,B_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    freq_0,w          ; Yes, get the freq_0 term
        addwf   AD9850_1,f        ;   and add it in to total
        btfss   STATUS,B_C        ; Does this addition result in a carry?
        goto    add7              ; No, continue with next freq term
        incfsz  AD9850_2,f        ; Yes, add one and check for another carry
        goto    add7              ; No, continue with next freq term
        incfsz  AD9850_3,f        ; Yes, add one and check for another carry
        goto    add7              ; No, continue with next freq term
        incf    AD9850_4,f        ; Yes, add one and continue
add7
        movf    freq_1,w          ; Use the freq_1 term
        addwf   AD9850_2,f        ; Add freq term to total in correct position
        btfss   STATUS,B_C        ; Does this addition result in a carry?
        goto    add8              ; No, continue with next freq term
        incfsz  AD9850_3,f        ; Yes, add one and check for another carry
        goto    add8              ; No, continue with next freq term
        incf    AD9850_4,f        ; Yes, add one and continue
add8
        movf    freq_2,w          ; Use the freq_2 term
        addwf   AD9850_3,f        ; Add freq term to total in correct position
        btfss   STATUS,B_C        ; Does this addition result in a carry?
        goto    add9              ; No, continue with next freq term
        incf    AD9850_4,f        ; Yes, add one and continue
add9
        movf    freq_3,w          ; Use the freq_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      ; 
        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
        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
        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,B_C        ; Was it zero?
        goto    send0             ; Yes, send zero
        bsf     PortB,DDS_dat     ; No, send one                              P
        bsf     PortB,DDS_clk     ; Toggle write clock                        P
        bcf     PortB,DDS_clk     ;                                           P
        goto    break             ; 
send0
        bcf     PortB,DDS_dat     ; Send zero                                 P
        bsf     PortB,DDS_clk     ; Toggle write clock                        P
        bcf     PortB,DDS_clk     ;                                           P
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,B_C        ;
        goto    next_byte         ;
        bsf     PortB,DDS_load    ; Send load signal to the AD9850            P
        bcf     PortB,DDS_load    ;                                           P
        return                    ;
;
; *****************************************************************************
; *                                                                           *
; * 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                               *
; *                                                                           *
; *****************************************************************************
;
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             ;   "     "
bin_loop
        bcf     STATUS,B_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,B_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,B_Z        ; Is loop count now zero?
        goto    adjust            ; No, go to adjust
        return                    ; Yes, EXIT 
; ============================================================================
adjust  ; 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.)
;
        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
        movlw   0x80              ; Point the LCD to first LCD digit location P
        call    cmnd2LCD          ; Send starting digit location to LCD
#IF LCDCHAR == 16
        movlw   ' '               ; Send a space
        call    data2LCD          ;   to position 1 of LCD
#ENDIF
;
; Running 4-bit mode, so need to send Most Significant Nibble first.
;
; Extract and send "XXXX" from byte containing "XXXXYYYY"
;  - Swap halves to get YYYYXXXX
;  - Mask with 0x0F to get 0000XXXX
;  - Add ASCII bias (0030XXXX)
;
        swapf   BCD_3,w           ; Swap 10MHz BCD digit into lower nibble of W
        andlw   0x0F              ; Mask for lower nibble only       (0000XXXX)
        addlw   0x30              ; Add offset for ASCII char set    (0030XXXX)
        call    data2LCD          ; Send byte in W to LCD
;
; Extract and send "YYYY" from byte containing "XXXXYYYY"
;   - Mask with 0x0F to get 0000YYYY
;   - Add offset for ASCII character set in LCD  (0030YYYY)
;
        movf    BCD_3,w           ; Put 1MHz BCD digit into lower nibble of W
        andlw   0x0F              ; Mask for lower nibble only       (0000YYYY)
        addlw   0x30              ; Add offset for ASCII char set    (0030YYYY)
        call    data2LCD          ; Send byte in W to LCD
; 
#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   0x0F              ; Mask for lower nibble only       (0000XXXX)
        addlw   0x30              ; Add offset for ASCII char set    (0030XXXX)
        call    data2LCD          ; Send byte in W to LCD
;
        movf    BCD_2,w           ; Put 10KHz BCD digit into lower nibble of W
        andlw   0x0F              ; Mask for lower nibble only       (0000YYYY)
        addlw   0x30              ; Add offset for ASCII char set    (0030YYYY)
        call    data2LCD          ; Send byte in W to LCD
;
        swapf   BCD_1,w           ; Swap 1KHz BCD digit into lower nibble of W
        andlw   0x0F              ; Mask for lower nibble only       (0000XXXX)
        addlw   0x30              ; Add offset for ASCII char set    (0030XXXX)
        call    data2LCD          ; Send byte in W to LCD
;
#IF LCDCHAR == 16                                                              
        movlw   '.'               ; Get a period
        call    data2LCD          ; Send byte in W to LCD
        movlw   0xC0              ; Point to LCD digit number nine
        call    cmnd2LCD          ; Send command byte in W to LCD
#ENDIF
;
        movf    BCD_1,w           ; Put 100 Hz BCD digit into lower nibble of W
        andlw   0x0F              ; Mask for lower nibble only       (0000YYYY)
        addlw   0x30              ; Add offset for ASCII char set    (0030YYYY)
        call    data2LCD          ; Send data byte in W to LCD
;
        swapf   BCD_0,w           ; Swap 10 Hz BCD digit into lower nibble of W
        andlw   0x0F              ; Mask for lower nibble only       (0000XXXX)
        addlw   0x30              ; Add offset for ASCII char set    (0030XXXX)
        call    data2LCD          ; Send data byte in W to LCD
;
        movf    BCD_0,w           ; Put 1 Hz BCD digit into lower nibble of W
        andlw   0x0F              ; Mask for lower nibble only       (0000YYYY)
        addlw   0x30              ; Add offset for ASCII char set    (0030YYYY)
        call    data2LCD          ; Send byte in W to LCD
;
#IF LCDCHAR == 16
        movlw   ' '               ; Send a space
        call    data2LCD          ;   to position 12 of LCD
;
        movlw   'k'               ; Send a 'k'
        call    data2LCD          ;   to position 13 of LCD
;
        movlw   'H'               ; Send an "H"
        call    data2LCD          ;   to position 14 of LCD
;
        movlw   'z'               ; Send a 'z'
        call    data2LCD          ;   to position 15 of LCD
;                                 ; Position 16 is blank
#ENDIF
;
        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,B_RP0      ; Switch to bank 1 for Tristate operation
        movlw   b'00001000'       ; Set RB3 input, others outputs             P
        movwf   TRISB             ;   via Tristate
        bcf     STATUS,B_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,B_Z        ; Is loop counter down to zero?
        goto    not_busy          ; If yes, return regardless
;OLD     btfsc   LCD_read,7        ; Is Busy Flag (RB7) in save byte clear?       
        btfsc   LCD_read,LCD_busy ; Busy Flag (RB3) in save byte clear?       P
        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,B_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,B_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)
;

;OLD     movlw   0xF0              ; Set up mask                                 
;OLD     andwf   PortB,f           ; Clear old RB7..RB4          
;OLD     movf    LCD_char,w        ; Put byte of data into W
;OLD     andlw   0xF0              ; Mask to give XXXX0000 in W
;OLD     iorwf   PortB,f           ; Send to RB7..RB4 without changing RB3..RB0
        movlw   0xF0              ; Set up mask                               P        
        andwf   PortB,f           ; Keep RB7..RB4 but clear old RB3..RB0          
        swapf   LCD_char,w        ; Put byte into W (reverse nibbles)         P
        andlw   0x0F              ; Mask to give 0000XXXX in W                P
        iorwf   PortB,f           ; To RB3..RB0 with RB7..RB4 unchanged       P
        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)
;
;OLD     movlw   0x0F              ; Set up mask                                 
;OLD     andwf   PortB,f           ; Clear old RB7..RB4
;OLD     swapf   LCD_char,w        ; Move LS nibble of data to MS position in W
;OLD     andlw   0xF0              ; Mask to give YYYY0000 in W
;OLD     iorwf   PortB             ; Send to RB7..RB4 without changing RB3..RB0
        movlw   0xF0              ; Set up mask                               P        
        andwf   PortB,f           ; Clear old RB3..RB0                        P
        movf    LCD_char,w        ; Move LS nibble of into W                  P
        andlw   0x0F              ; Mask to give 0000YYYY in W                P
        iorwf   PortB,f           ; To RB3..RB0 with RB7..RB4 unchanged       P
        bsf     PortB,LCD_e       ; Pulse the E line high,
        nop                       ;   wait, 
        bcf     PortB,LCD_e       ;   and drop it again
        return
;
; *****************************************************************************
; *                                                                           *
; * Purpose:  Write the byte of data at EEdata to the EEPROM at address       *
; *           EEadr.                                                          *
; *                                                                           *
; *   Input:  The values at EEdata and EEadr.                                 *
; *                                                                           *
; *  Output:  The EEPROM value is updated.                                    *
; *                                                                           *
; *****************************************************************************
;
write_EEPROM
        bsf     STATUS,B_RP0      ; Switch to bank 1
        bsf     EEdata,WREN       ; Set the EEPROM write enable bit
        movlw   0x55              ; Write 0x55 and 0xAA to EEPROM
        movwf   EEadr             ;   control register, as required
        movlw   0xAA              ;   for the write
        movwf   EEadr             ; 
        bsf     EEdata,WR         ; Set WR to initiate write
bit_check
        btfsc   EEdata,WR         ; Has the write completed?
        goto    bit_check         ; No, keep checking
        bcf     EEdata,WREN       ; Clear the EEPROM write enable bit
        bcf     STATUS,B_RP0      ; Switch to bank 0 
        incf    EEadr,f           ; Increment the EE write address
        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.                                            *
; *                                                                           *
; *****************************************************************************
;
read_EEPROM
        bsf     STATUS,B_RP0      ; Switch to bank 1
        bsf     EEdata,RD         ; Request the read
        bcf     STATUS,B_RP0      ; Switch to bank 0
        incf    EEadr,f           ; Increment the read address
        return                    ; Return to the caller
;
; *****************************************************************************
; *                                                                           *
; * 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                        *
; *                                                                           *
; *   Input:  None                                                            *
; *                                                                           *
; *  Output:  None                                                            *
; *                                                                           *
; *****************************************************************************
;
wait_a_sec  ; ****** Entry point ******    
        call    wait_256ms        ;       
        call    wait_256ms        ;       
        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
                                  ; Fall through into wait loops
;
; 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

