; **************************************************************************** ; * Signal Generator (VFO) with Direct Digital Synthesis * ; * Version Dual PIC DDS 7a7 * ; * Jan 11, 2001 * ; * Dual PIC version 125MHz Clock RT * * ; **************************************************************************** ; Description: ; This is the control program for a two processor DDS VFO built with an AD9850 ; DDS chip, a 512 step/rev shaft encoder, a push button switch and a Liquid Crystal ; Display. It is adapted from software written by NJ-QRP Club members. See author ; details below. ; ; This routine drives the DDS. A separate rountine is used to manage the LCD. ; ; BAND MEMORIES an external push button switch allows the frequency to be ; cycled around the HF ham bands. ; ; CALIBRATE MODE is entered if the external push button is pressed during ; power on. The display is set to 10,000.000 CAL and remains fixed, even as ; adjustments are being made. If the push button 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 external ; push button and turn the shaft encoder one more time. The calibrated value ; of "osc" will then be stored in EEPROM memory. ; ; IF OFFSET is added or subtracted depending upon state of RA4. This operates ; on the DDS data to shift the frequency leaving the display frequency constant. ; Two offset values, IF_Add and IF_Sub allow a BFO offset to be programmed. ; This feature was originally intended for use with ladder filters which ; favour LSB operation. By switching the LO injection USB and LSB operation ; is possible. Ron Taylor G4GXO October 1999 ; ; ; ;****************************************************************************** ; Author - Curtis W. Preuss - WB2V ; ; Modification History ; 8/19/98 - Version 1 - Initial Version by Curtis W. Preuss - WB2V ; 12/xx/98 - Version 2 - Converted to MPASM by Bruce Stough, AA0ED ; 4/21/99 - Version 3 - Fixed and modified by Bruce Stough, AA0ED and ; Craig Johnson, AA0ZZ ; ; Changes by Ron Taylor, G4GXO ; ; Version Dual_7 ("a" is PIC 1 DDS software, "b" is PIC 2 LCD software) ; ;IF Shift IF_Offset routine added and poll_encoder routine merged ; with main to simplify IF_Offset branching and overcome ; a stack overflow problem. ; ;Busy Check Timing sequence modided to overcome a compatibility ; Problem with certain displays. ; ;Dual PIC variant Original program split into DDS and Display routines. ; A serial interface routine allows both PIC's to pass data ; between each other. Tuning rate is fixed at 10Hz steps ; with 1Hz fine tune selected by RA2. A 512 step/rev shaft ; encoder gives fast 10kHz/rev tuning rate. Frequency data ; is transferred to PIC2 when ever it is free. ; ;Delay Routines Adapted for 8MHz clock. ; ;Version 7 Tidy up and insert of timing measurement probes ; ;***************************************************************************** ; ; Target Controller - PIC16F84 ; __________ ; ENCODER SWITCH--RA2 |1 18| RA1---------ENCODER A ; PB SWITCH-------RA3 |2 17| RA0---------ENCODER B ; IF Shift--------RA4 |3 16| OSC1--------XTAL ; Ground--------!MCLR |4 15| OSC2--------XTAL ; Ground----------Vss |5 14| VDD---------+5 V ; --------RB0 |6 13| RB7---------DDS/PIC2 LCD DATA ; -------RB1 |7 12| RB6---------DDS LOAD ; ----------RB2 |8 11| RB5---------DDS DATA CLOCK ; PIC2 Busy ------RB3 |9 10| RB4---------PIC 2 LCD DATA CLOCK ; ---------- ;Note. Port B inputs (except encoder) require 10K pull up resistors to +5v. ; ; ;Shaft Encoder: HP Agilent HEDS 9100 optical sensor ; HEDS 5120-A06 2 Channel Code Wheel ; (Farnell) ; ;Mounting made from PCB stock, shaft is 1/4" brass tube from model shop carefully ;rubbed down to make an interference fit into two 1/4" instrumentation bearings ;epoxy'd into PCB frame. ; ; ;***************************************************************************** ; ; ; **************************************************************************** ; * 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_ON & _XT_OSC ; ; **************************************************************************** ; * 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 . ; ; The values for common oscillator frequencies are as follows: ; ; Frequency ref_osc_3 ref_osc_2 ref_osc_1 ref_osc_0 ; 125.00 MHz 0x22 0x5C 0x17 0xD0 ; 120.00 MHz 0x23 0xCA 0x98 0xCE ; 100.00 MHz 0x2A 0xF3 0x1D 0xC4 ; 90.70 MHz 0x2F 0x5A 0x82 0x7A ; 66.66 MHz 0x40 0x6E 0x52 0xE7 ; 66.00 MHz 0x41 0x13 0x44 0x5F ; 50.00 MHz 0x55 0xE6 0x3B 0x88 ; ; To calculate other values: ; ref_osc_3 = (2^32 / oscillator_freq_in_Hertz). ; ref_osc_2, ref_osc_1, and ref_osc_0 are the fractional part of ; (2^32 / oscillator_freq_in_Hertz) times 2^24. ; Note: 2^32 = 4294967296 and 2^24 = 16777216 ; ; For example, for a 120 MHz clock: ; ref_osc_3 is (2^32 / 120 x 10^6) = 35.791394133 truncated to 35 (0x23) ; ref_osc_2 is the high byte of (.791394133 x 2^24) = 13277390.32 ; 13277390.32 = 0xCA98CE, so high byte is CA. ; ref_osc_1 is the next byte of 0xCA98CE, or 98 ; ref_osc_0 is the last byte of 0xCA98CE, or CE ; ;==== Currently set for 125 MHz Oscillator ======= ref_osc_3 equ 0x22 ; Most significant osc byte ref_osc_2 equ 0x5C ; Next byte ref_osc_1 equ 0x17 ; Next byte ref_osc_0 equ 0xD0 ; 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 ; ; **************************************************************************** ; * IF Offset Frequencies * ; **************************************************************************** ; Two offsets are used to allow incorporation of a BFO offset typically 3kHz ; If BFO offset not needed set both offsets the same. ; ; Offset Frequencies set for 10MHz Ladder Filter 10,000 kHz and 9,997kHz ; ; IF Frequency used when adding offset ; IF_Add_3 equ 0x00 ;MSb IF_Add_2 equ 0x98 IF_Add_1 equ 0x96 IF_Add_0 equ 0x80 ;LSb ; ; IF Frequency used when subtracting offset ; IF_Sub_3 equ 0x00 ;MSb IF_Sub_2 equ 0x98 IF_Sub_1 equ 0x8A IF_Sub_0 equ 0xC8 ;LSb ; ; ; ; ; **************************************************************************** ; * 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 DATA ref_osc_0 DATA ref_osc_1 DATA ref_osc_2 DATA ref_osc_3 ; ; 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,0,0,0,0 ; ; **************************************************************************** ; * 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_load equ 0x00 ; Update pin on AD9850 LCD_rs equ 0x01 ; 0=instruction, 1=data LCD_rw equ 0x02 ; 0=write, 1=read LCD_e equ 0x03 ; 0=disable, 1=enable DDS_clk equ 0x05 ; AD9850 write clock DDS_dat equ 0x07 ; AD9850 serial data input ; ; A register bits: ; pb_switch equ 0x03 ; Calibrate Push Button, (active low) ; ; **************************************************************************** ; * 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 DDS_freq_0 ; Freq input to Calc_DDS DDS_freq_1 ; After IF offset DDS_freq_2 ; (4 bytes) DDS_freq_3 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 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 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 step_divider ; Counter used to slow tuning rate when changing band IF_new ; IF switch new state IF_old ; IF switch old state 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 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 clrf PortA ; Initialise Port A clrf PortB ; Initialise Port B bsf STATUS,B_RP0 ; Switch to bank 1 bsf 0x01,7 ; Disable weak pullups movlw 0xFF ; Tristate port A movwf TRISA ; movlw 0x08 ; Set Port B 3 as input, 0..2,4..7 as outputs movwf TRISB ; bcf STATUS,B_RP0 ; Switch back to bank 0 ; ; Enter Calibrate Mode if push button is pressed while turning the ; power on. ; btfsc PortA,pb_switch ; Is the switch pressed? 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. ; movlw default_0 ; Get the least significant byte movwf freq_0 ; Save it movlw default_1 ; Get the next byte movwf freq_1 ; Save it movlw default_2 ; And the next movwf freq_2 ; Save it movlw default_3 ; Get the most significant byte movwf freq_3 ; Save it ; ; Display the power on frequency. ; call send_LCD_word ; Send freq data to PIC 2 if free ; ; Send power on frequency to the DDS chip. ; call IF_Offset ; Adjust base freq data for IF call calc_dds_word ; Convert to delta value call send_dds_word ; Send the power-on frequency to the ; AD9850 in serial mode ; ; 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 andwf ren_read,w ; Get encoder bits movwf ren_old ; Save in ren_old ; ; Initialize variables. ; 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. * ; * * ; * The fixed incremental frequency fstep is added or subtracted * ; * from the current VFO frequency stored in freq. The IF offset * ; * is added or subtracted depending upon RA4 state. 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. * ; * The binary frequency is sent to the PIC2 for LCD display * ; * by the send_LCD_word subroutine. * ; * If the push button is pressed while turning the encoder then * ; * 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 ; Start of main loop ; movlw 0xFF ; Load step divider with 255 for band change routine movwf step_divider ; to give slow band change rate poll_encoder ; Capture and analyse encoder and IF switch states ; ***************************************************************************** ; * Purpose: This routine does the following: * ; * 1. Clears the watchdog timer. * ; * 2. 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_new -> the current encoder bits * ; * last_dir -> the last direction (0 = down, 2 = up) * ; * * ; ***************************************************************************** ; read_encoder clrwdt ; Reset the watchdog timer call send_LCD_word ; Check if PIC 2 ready to receive freq data check_if ; Check state of IF offset line (RA4) movf PortA,w ; Get the current encoder value movwf ren_read ; Save it movlw 0x10 ; Get IF offset mask andwf ren_read,w ; Isolate IF offset bit movwf IF_new ; Save new value xorwf IF_old,w ; Has it changed? btfsc STATUS,B_Z ; goto no_inc ; No, jump to encoder test movf IF_new,w ; Update historic IF status movwf IF_old goto main1 ; Shift IF ; ; Test encoder state ; no_inc movf PortA,w ; Get the current encoder value movwf ren_read ; Save it movlw 0x03 ; Get encoder mask andwf ren_read,w ; Isolate encoder bits movwf ren_new ; Save new value xorwf ren_old,w ; Has it changed? btfsc STATUS,B_Z ; goto read_encoder ; No, keep looking until it changes ; ; Determine which direction the encoder turned. ; bcf STATUS,B_C ; Clear the carry bit rlf ren_old,f ; movf ren_new,w ; xorwf ren_old,f ; movf ren_old,w ; andlw 0x02 ; movwf next_dir ; xorwf last_dir,w ; ; ; Prevent encoder slip from giving a false change in direction. ; btfsc STATUS,B_Z ; Zero? goto pe_continue ; No slip; keep going movf next_dir,w ; Yes, update direction movwf last_dir ; movf ren_new,w ; Save the current encoder bits for next time movwf ren_old ; goto read_encoder ; Try again pe_continue btfsc ren_old,1 ; Are we going down? goto up2 ; No, indicate we are going up clrf last_dir ; Yes, clear last_dir goto exit3 ; Finish and return up2 movlw 0x02 ; Set UP value in last_dir movwf last_dir ; exit3 movf ren_new,w ; Get the current encoder bits movwf ren_old ; Save them in ren_old for the next time ; ; Check Band Switch ; btfsc ren_read,3 ; Change band? goto Tuning_step ; No, goto Tuning_step decfsz step_divider ; Decrement step counter (pre-loaded with 255) to 0 goto poll_encoder ; to slow down band change rate with 500 position goto change_band ; optical encoder ; ; Determine step size to use (10 Hz or 1 Hz). Tuning_step clrf fstep_3 ; Set tuning rate to 10 Hz steps by clrf fstep_2 ; setting fstep to 0Ah (10). clrf fstep_1 ; movlw 0x0A ; movwf fstep_0 ; ; ; Fine tuning routine ; btfss ren_read,2 ; Is the encoder switch pressed? goto go_step ; No, use the 10 Hz step movlw 0x01 ; Yes, set the step value to 1 Hz movwf fstep_0 ; by setting fstep_0 to 0x01 goto go_step ; Use the 1 Hz step ; ; go_step ; 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 write ; Update LCD and DDS up call add_step ; Add fstep to freq call check_add ; Make sure we did not exceed the maximum goto write ; Update LCD and DDS ; ; ***************************************************************************** ; * * ; * 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 send_LCD_word ; Send freq data to PIC 2 if free main1 bsf PortB,1 ; *** Test Probe to measure timimg call IF_Offset ; Add or subtract IF offset to DDS data bcf PortB,1 bsf PortB,2 ; *** Test Probe to measure timimg call calc_dds_word ; Find the control word for the DDS chip bcf PortB,2 bsf PortB,0 ; *** Test probe for timing measure call send_dds_word ; Send the control word to the DDS chip bcf PortB,0 ; *** Test probe for timing measure goto main ; Continue polling the encoder (endless loop) ; ; ***************************************************************************** ; * * ; * 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: 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 is entered at start up if the push button is * ; * pressed. "10,000.00 CAL" is displayed on the LCD, and the * ; * 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 movwf freq_0 ; setting freq to the binary equivalent movlw 0x96 ; of 10,000,000. 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 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 ren_read,3 ; Is the button still pressed? 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 ; ; ***************************************************************************** ; * * ; * 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 bsf PortB, 0 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 DDS_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 DDS_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 DDS_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 DDS_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 bcf PortB,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. * ; * * ; * * ; * This routine has been optimised for speed by replicating the bit shift * ; * and write routine rather than use a loop. The penalty is a less efficient * ; * use of memory - but with the removal of the LCD routines this is not * ; * a problem. RT * ; * * ; * * ; ***************************************************************************** send_dds_word movlw AD9850_0 ; Point FSR at AD9850 movwf FSR ; next_byte movf INDF,w ; movwf byte2send ; ; ;Send the byte bit by bit ;bit 0 rrf byte2send,f ; Test if next bit is 1 or 0 btfss STATUS,B_C ; Was it zero? goto set00 ; Yes, send zero bsf PortB,7 ; No, send one goto sendbit0 set00 bcf PortB,7 ; Send zero sendbit0 bsf PortB,5 ; Toggle write clock bcf PortB,5 ; ;bit 1 rrf byte2send,f ; Test if next bit is 1 or 0 btfss STATUS,B_C ; Was it zero? goto set01 ; Yes, send zero bsf PortB,7 ; No, send one goto sendbit1 set01 bcf PortB,7 ; Send zero sendbit1 bsf PortB,5 ; Toggle write clock bcf PortB,5 ; ;bit 2 rrf byte2send,f ; Test if next bit is 1 or 0 btfss STATUS,B_C ; Was it zero? goto set02 ; Yes, send zero bsf PortB,7 ; No, send one goto sendbit2 set02 bcf PortB,7 ; Send zero sendbit2 bsf PortB,5 ; Toggle write clock bcf PortB,5 ; ;bit 3 rrf byte2send,f ; Test if next bit is 1 or 0 btfss STATUS,B_C ; Was it zero? goto set03 ; Yes, send zero bsf PortB,7 ; No, send one goto sendbit3 set03 bcf PortB,7 ; Send zero sendbit3 bsf PortB,5 ; Toggle write clock bcf PortB,5 ; ;bit 4 rrf byte2send,f ; Test if next bit is 1 or 0 btfss STATUS,B_C ; Was it zero? goto set04 ; Yes, send zero bsf PortB,7 ; No, send one goto sendbit4 set04 bcf PortB,7 ; Send zero sendbit4 bsf PortB,5 ; Toggle write clock bcf PortB,5 ; ;bit 5 rrf byte2send,f ; Test if next bit is 1 or 0 btfss STATUS,B_C ; Was it zero? goto set05 ; Yes, send zero bsf PortB,7 ; No, send one goto sendbit5 set05 bcf PortB,7 ; Send zero sendbit5 bsf PortB,5 ; Toggle write clock bcf PortB,5 ; ;bit 6 rrf byte2send,f ; Test if next bit is 1 or 0 btfss STATUS,B_C ; Was it zero? goto set06 ; Yes, send zero bsf PortB,7 ; No, send one goto sendbit6 set06 bcf PortB,7 ; Send zero sendbit6 bsf PortB,5 ; Toggle write clock bcf PortB,5 ; ;bit 7 rrf byte2send,f ; Test if next bit is 1 or 0 btfss STATUS,B_C ; Was it zero? goto set07 ; Yes, send zero bsf PortB,7 ; No, send one goto sendbit7 set07 bcf PortB,7 ; Send zero sendbit7 bsf PortB,5 ; Toggle write clock bcf PortB,5 ; 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,6 ; Send load signal to the AD9850 bcf PortB,6 ; return ; ; ***************************************************************************** ; * * ; * Purpose: This routine tests the state of PIC 2 and if low (free) * ; * sends the LCD data to PIC 2 using a serial data transfer and * ; * handshaking over busy line. The clock and data lines are * ; * shared with the DDS, a select line (RB4) sets the destination * ; * to PIC2. This is a modified version of the DDS load routine. * ; * * ; * Input: freq3... freq0 * ; * * ; * Output: The display PIC is updated. * ; * * ; ***************************************************************************** ; send_LCD_word btfsc PortB,3 ; Test state of PIC 2 busy line goto exit_LCD ; High (busy) so jump to exit_LCD movlw freq_0 ; Point FSR at freq movwf FSR ; next_LCD_byte movf INDF,w ; movwf byte2send ; movlw 0x08 ; Set counter to 8 movwf bit_count ; next_LCD_bit rrf byte2send,f ; Test if next bit is 1 or 0 btfss STATUS,B_C ; Was it zero? goto send_LCD_0 ; Yes, send zero send_LCD_1 bsf PortB,7 ; No, send one bsf PortB,4 ; Toggle write clock high wait_for_receipt1 btfss PortB,3 ; Wait for PIC 2 Busy line to go high (busy) confirming goto wait_for_receipt1 ; receipt of bit before dropping clock and waiting for free condition bcf PortB,4 ; Toggle write clock low wait_for_free1 btfsc PortB,3 ; Wait for PIC 2 Busy line to go low (free) confirming goto wait_for_free1 ; that it's ready to proceed with next bit goto break_LCD send_LCD_0 bcf PortB,7 ; Send zero bsf PortB,4 ; Toggle write clock high wait_for_receipt2 btfss PortB,3 ; Wait for PIC 2 Busy line to go high (busy) confirming goto wait_for_receipt2 ; receipt of bit before dropping clock and waiting for free condition bcf PortB,4 ; Toggle write clock low wait_for_free2 btfsc PortB,3 ; Wait for PIC 2 Busy line to go low (free) confirming goto wait_for_free2 ; that it's ready to proceed with next bit break_LCD decfsz bit_count,f ; Has the whole byte been sent? goto next_LCD_bit ; No, keep going. incf FSR,f ; Start the next byte unless finished movlw freq_3+1 ; Next byte (past the end) subwf FSR,w ; btfss STATUS,B_C ; goto next_LCD_byte ; exit_LCD return ; Return to caller ; ; ; ***************************************************************************** ; * * ; * 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 ; ; ***************************************************************************** ; IF Offset Routine ; ; This uses modified versions of the 32 bit add and subtract routines to shift ; the DDS frequency data by the value held if the IF_Add and IF_Sub constants. ; If RA4 is high the IF frequency in Hz is added, if RA4 is low the IF ; frequency is subtracted. Negative results in the subtraction process are ; made positive by a two's compliment subtroutine. This produces a positive ; value which is the difference between the displayed frequency and the IF ; frequency. ; ; ***************************************************************************** ; ; ; IF_Offset ; Load freq into DDS_freq movf freq_0,w ; Copy current "freq" into "DDS_freq" variables movwf DDS_freq_0 ; to allow offset to be added without movf freq_1,w ; affecting display value which is based movwf DDS_freq_1 ; upon content of "freq" movf freq_2,w movwf DDS_freq_2 movf freq_3,w movwf DDS_freq_3 btfss PortA,4 ; Test state of IF offset switch goto sub_IF ; Low so subtract goto add_IF ; High so add IF_exit1 return ; Return to Main ; ***************************************************************************** ; * * ; * 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 DDS_freq. * ; * * ; ***************************************************************************** add_IF movlw IF_Add_0 ; Get low byte of the increment addwf DDS_freq_0,f ; Add it to the low byte of freq btfss STATUS,B_C ; Any carry? goto IFadd1 ; No, add next byte incfsz DDS_freq_1,f ; Ripple carry up to the next byte goto IFadd1 ; No new carry, add next byte incfsz DDS_freq_2,f ; Ripple carry up to the next byte goto IFadd1 ; No new carry, add next byte incf DDS_freq_3,f ; Ripple carry up to the highest byte IFadd1 movlw IF_Add_1 ; Get the next increment byte addwf DDS_freq_1,f ; Add it to the next higher byte btfss STATUS,B_C ; Any carry? goto IFadd2 ; No, add next byte incfsz DDS_freq_2,f ; Ripple carry up to the next byte goto IFadd2 ; No new carry, add next byte incf DDS_freq_3,f ; Ripple carry up to the highest byte IFadd2 movlw IF_Add_2 ; Get the next to most significant increment addwf DDS_freq_2,f ; Add it to the freq byte btfss STATUS,B_C ; Any carry? goto IFadd3 ; No, add last byte incf DDS_freq_3,f ; Ripple carry up to the highest byte IFadd3 movlw IF_Add_3 ; Get the most significant increment byte addwf DDS_freq_3,f ; Add it to the most significant freq goto IF_exit1 ; Finished ; ***************************************************************************** ; * * ; * Purpose: Subtract the IF from freq, checking that it does * ; * not go below zero. * ; * * ; * Input: The values in IF and DDS_freq. * ; * * ; * Output: The updated value in DDS_freq. * ; * * ; ***************************************************************************** ; sub_IF comf DDS_freq_0,f ; Subtraction of fstep from comf DDS_freq_1,f ; freq is done by adding the comf DDS_freq_2,f ; twos compliment of freq comf DDS_freq_3,f ; to IF incfsz DDS_freq_0,f ; Increment last byte goto IF_comp_done ; Non-zero, continue incfsz DDS_freq_1,f ; Increment next byte goto IF_comp_done ; Non-zero, continue incfsz DDS_freq_2,f ; Increment next byte goto IF_comp_done ; Non-zero, continue incf DDS_freq_3,f ; Increment the high byte IF_comp_done ; ; Now add the compliment of DDS_freq and IF ; movlw IF_Add_0 ; Get low byte of the increment addwf DDS_freq_0,f ; Add it to the low byte of freq btfss STATUS,B_C ; Any carry? goto IFSadd1 ; No, add next byte incfsz DDS_freq_1,f ; Ripple carry up to the next byte goto IFSadd1 ; No new carry, add next byte incfsz DDS_freq_2,f ; Ripple carry up to the next byte goto IFSadd1 ; No new carry, add next byte incf DDS_freq_3,f ; Ripple carry up to the highest byte IFSadd1 movlw IF_Add_1 ; Get the next increment byte addwf DDS_freq_1,f ; Add it to the next higher byte btfss STATUS,B_C ; Any carry? goto IFSadd2 ; No, add next byte incfsz DDS_freq_2,f ; Ripple carry up to the next byte goto IFSadd2 ; No new carry, add next byte incf DDS_freq_3,f ; Ripple carry up to the highest byte IFSadd2 movlw IF_Add_2 ; Get the next to most significant increment addwf DDS_freq_2,f ; Add it to the freq byte btfss STATUS,B_C ; Any carry? goto IFSadd3 ; No, add last byte incf DDS_freq_3,f ; Ripple carry up to the highest byte IFSadd3 movlw IF_Add_3 ; Get the most significant increment byte addwf DDS_freq_3,f ; Add it to the most significant freq ; ; If the frequency has gone negative, two's compliment it. ; btfss DDS_freq_3,7 ; Is high order frequency byte "negative"? goto IF_exit1 ; No, keep going IF_comp comf DDS_freq_0 ; Yes, two's compliment result comf DDS_freq_1 ; to make positive difference comf DDS_freq_2 ; comf DDS_freq_3 ; incfsz DDS_freq_0,f ; Increment last byte goto IF_exit2 ; Non-zero, continue incfsz DDS_freq_1,f ; Increment next byte goto IF_exit2 ; Non-zero, continue incfsz DDS_freq_2,f ; Increment next byte goto IF_exit2 ; Non-zero, continue incf DDS_freq_3,f ; Increment the high byte IF_exit2 goto IF_exit1 ; Subtract finished END