; ******************************************************************************* ; * * ; * DDS_VFO_8c.asm copyright 2014 Terry Mowles VK5TM * ; * * ; * This code may be used for personal, non-profit use only. * ; * * ; * Commercial use of this code or any derivatives of it is expressly forbidden.* ; * Use in any profit making enterprise of any kind is also expressly forbidden.* ; * * ; * This code is in part based on the works of the following: * ; * Curtis W. Preuss - WB2V * ; * Bruce Stough - AA0ED * ; * Craig Johnson - AA0ZZ * ; * * ; ******************************************************************************* ; ; ******************************************************************************* ; * * ; * This code uses a 12F629 as a limited range VFO * ; * Features: * ; * Suitable for AD9850 or AD9851 DDS chips * ; * Upper and lower frequency limit * ; * Frequency steps of 100Hz/1kHz * ; * Frequency save once the encoder has stopped turning for 2Secs * ; * Calibrate routine to account for DDS Xtal frequency variations * ; * * ; * NOTE: Once the calibrate routine has been done, entry into * ; * the calibrate routine is ignored on power up. The PIC * ; * must be reprogrammed to perform another calibration. * ; * * ; * See www.vk5tm.com for more information * ; * * ; ******************************************************************************* ; ; ******************************************************************************* ; * * ; * Target Controller - PIC12F629 * ; * ----\/---- * ; * V+---------- |1 8| GND * ; * ENCODER A---GP5 |2 7| GP0---DDS DATA * ; * ENCODER B---GP4 |3 6| GP1---DDS FU_UP * ; * CAL---------GP3 |4 5| GP2---DDS W_CLOCK * ; * ---------- * ; ******************************************************************************* ; !!----- SELECT ONE OF THESE DDS TYPES AND TURN THE OTHER OFF -----!! #define AD9850 ; Using the AD9850 ;#define AD9851 ; Using the AD9851 ; ******************************************************************************* ; * Device type and options. * ; ******************************************************************************* ; processor 12F629 radix dec errorlevel -302 ; Skip out of bank nuisance messages ; ******************************************************************************* ; * Configuration fuse information for 12F629 * ; ******************************************************************************* include __config _CP_OFF&_CPD_OFF&_BODEN_OFF&_MCLRE_OFF&_PWRTE_ON&_WDT_OFF&_FOSC_INTRCIO ; ; ******************************************************************************* ; * General equates. These may be changed to accommodate the reference * ; * clock frequency, the desired upper frequency limit, the desired lower * ; * 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 ; ; 180.00 MHz 0x17 0xDC 0x65 0xDF ; 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 ; 30.00 MHz 0x8F 0x2A 0x63 0x39 ; ; 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 ref_osc_2 is CA. ; ref_osc_1 is the next byte of 0xCA98CE, or 98 ; ref_osc_0 is the last byte of 0xCA98CE, or CE ; ; For example, for a 180 MHz clock: ; ref_osc_3 is (2^32 / 180 x 10^6) = 23.860929422 truncated to 23 (0x17) ; ref_osc_2 is the high byte of (.860929422 x 2^24) = 14443998 ; 14443998 = 0xDC65DE, so high byte is DC. ; ref_osc_1 is the next byte of 0xDC65DE, or 65 ; ref_osc_0 is the last byte of 0xDC65DE, or DE ; #ifdef AD9850 ; 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 #endif #ifdef AD9851 ; For 180 MHz (30 MHz clock and 6x multiplier) ref_osc_3 equ 0x17 ; Most significant osc byte ref_osc_2 equ 0xDC ; Next byte ref_osc_1 equ 0x65 ; Next byte ref_osc_0 equ 0xDE ; Least significant byte #endif ; ******************************************************************************* ; Change limits to suit ; ; Limit_0...3 contains the upper limit frequency as a 32 bit integer. ; limit_3 equ 0x00 ; Most significant byte for 7.200 MHz limit_2 equ 0x6D ; Next byte limit_1 equ 0xDD ; Next byte limit_0 equ 0x00 ; Least significant byte ; ; Low_limit_3..0 contains the lower frequency limit as a 32 bit integer ; limit_low_3 equ 0x00 ; Most significant byte for 7.000 MHz limit_low_2 equ 0x6A ; Next byte limit_low_1 equ 0xCF ; Next byte limit_low_0 equ 0xC0 ; Least significant byte ; ; Default contains the default startup frequency as a 32 bit integer. ; default_3 equ 0x00 ; Most significant byte for 7.030 MHz default_2 equ 0x6B ; Next byte default_1 equ 0x44 ; Next byte default_0 equ 0xF0 ; Least significant byte ; ; Frequency at which Calibration will take place ; cal_freq_3 equ 0x00 ; Most significant byte for 7.030 MHz cal_freq_2 equ 0x6B ; Next byte cal_freq_1 equ 0x44 ; Next byte cal_freq_0 equ 0xF0 ; Least significant byte ; ; ******************************************************************************* ; ; ******************************************************************************* ; * Setup the initial constant, based on the frequency of the reference * ; * oscillator. This can be tweaked with the calibrate function. * ; * Stored in EEPROM * ; ******************************************************************************* ; ORG 0x2100 ; ref_osc bytes must be first 4 bytes of EEPROM 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 DATA default_0 ; startup -> freq_0 DATA default_1 ; startup -> freq_1 DATA default_2 ; startup -> freq_2 DATA default_3 ; startup -> freq_3 DATA 0,0,0,0,0,0,0 ; ; ******************************************************************************* ; * Assign names to IO pins. * ; ******************************************************************************* ; #DEFINE DDS_clk GPIO,2 ; AD9850/AD9851 write clock #DEFINE DDS_dat GPIO,0 ; AD9850/AD9851 serial data input #DEFINE DDS_load GPIO,1 ; Update pin on AD9850/AD9851 ; ------------Interupt defines--------------------------------------------------- ; w_temp EQU 0x5E ; variable used for context saving status_temp EQU 0x5F ; variable used for context saving ; ; ******************************************************************************* ; * Allocate variables in general purpose register space * ; ******************************************************************************* ; CBLOCK 0x20 ; Start Data Block AD9851_0 ; AD9850/AD9851 control word AD9851_1 ; (5 bytes) AD9851_2 AD9851_3 AD9851_4 fstep_0 ; Frequency inc/dec fstep_1 ; (4 bytes) fstep_2 fstep_3 fshift_0 ; Valeur shift TX-RX 600Hz fshift_1 fshift_2 fshift_3 low_limit_3 ; Low frequency limit low_limit_2 ; (4 bytes) low_limit_1 low_limit_0 mult_count ; Used in calc_dds_word bit_count ; " byte2send ; osc_temp_0 ; Oscillator frequency osc_temp_1 ; (4 bytes) osc_temp_2 osc_temp_3 timer1 ; Used in delay routines timer2 ; " ren_new ; New value of encoder pins A and B ren_old ; Old value of encoder pins A and B ren_read ; Encoder pins A and B and switch pin last_dir ; Indicates last direction of encoder next_dir ; Indicates expected direction enc_counter ; Divide by 4 counter for mechanical encoder saved ; Flags for frequency save routine ; saved,0 used in read encoder routine to jump flag for frequency save ; =1 calibrate mode active ; =0 calibrate mode not active ; saved,1 test if frequency save done ; =1 frequency save done ; =0 frequency save not done ; saved,2 used in interupt routine ; saved,3 used to tell if shift 600Hz is active CNT1 ; Counter for interrupt routine freq_0 ; Frequency (hex) freq_1 ; (4 bytes) freq_2 ; freq_3 ; osc_0 ; Current oscillator osc_1 ; (4 bytes) osc_2 osc_3 cal_flag ; Flag to test if CAL done. Bit 0 = 1 if done ENDC ; End of Data Block ; ******************************************************************************* ; * The 12F629 resets to 0x00. * ; * The Interrupt vector is at 0x04. * ; ******************************************************************************* ; ORG 0x0000 goto start ; Jump to main program ORG 0x0004 ; interrupt routine for approx 2 second counter movwf w_temp ; save off current W register contents movf STATUS,w ; move status register into W register movwf status_temp ; save off contents of STATUS register decfsz CNT1,F ; Decrease CNT1. If zero, skip next instruction goto not_yet ; Not zero goto not yet bsf saved,2 ; set interrupt timed out flag goto exit_interrupt ; not_yet bcf INTCON,T0IF ; Clear Timer0 interrupt flag, otherwise interrupt will occur again bsf INTCON,T0IE ; Enable Timer0 interrupt again movlw 39 movwf TMR0 ; Reload Timer0 ; exit_interrupt ; movf status_temp,w ; retrieve copy of STATUS register movwf STATUS ; restore pre-isr STATUS register contents swapf w_temp,f swapf w_temp,w ; restore pre-isr W register contents retfie ; Enable general interrupts and return ; ; ******************************************************************************* ; * * ; * Purpose: This is the start of the program. It 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...0 * ; * definitions above, and relies on the reference oscillator * ; * constant defined in ref_osc_3...ref_osc_0. * ; * * ; * Output: Normal VFO operation. * ; * * ; ******************************************************************************* ; start movlw 0x0A ; On fixe le pas du VFO à 10Hz movwf fstep_0 movlw 0x00 movwf fstep_1 movlw 0x00 movwf fstep_2 movlw 0x00 movwf fstep_3 movlw 0x07 ; Code to turn off the analog comparitors movwf CMCON ; bsf STATUS,RP0 ; Switch to bank 1 call 0x3FF ; retrieve factory calibration value movwf OSCCAL ; update register with factory cal value movlw b'00111000' ; GPIO 3,4,5 inputs 0,1,2 outputs movwf TRISIO ; bcf STATUS,RP0 ; Switch back to bank 0 clrf cal_flag ; ; ; Initialize DDS Module with zero freq ; clrf AD9851_0 ; AD9850/51 control word clrf AD9851_1 ; (5 bytes) clrf AD9851_2 clrf AD9851_3 clrf AD9851_4 call send_dds_word ; Send it to the DDS call send_dds_word ; twice to be sure ; movlw limit_low_3 ; Most significant byte for lower limit movwf low_limit_3 movlw limit_low_2 ; Next byte movwf low_limit_2 movlw limit_low_1 ; Next byte movwf low_limit_1 movlw limit_low_0 ; Least significant byte movwf low_limit_0 ; ; Enter Calibrate Mode if GPIO,3 is low when turning the power on and CAL not already done. ; Test flag to see if CAL already done. Jump over this section if done. movlw 8 ; bsf STATUS,RP0 ; Switch to bank 1 movwf EEADR ; Point to flag location in EEprom call read_EEPROM ; Read EEprom at address 4 bcf STATUS,RP0 ; Switch to bank 0 movwf cal_flag ; Move data to register btfsc cal_flag,0 ; Test set_flag,0 - will be 0 if CAL not done goto read_EEocs ; set_flag,0 is 1 so CAL already done btfss GPIO,3 ; Cal pushbutton pressed call calibrate ; Yes,call calibration routine ; ; Get the reference oscillator constant from the EEPROM. ; read_EEocs ; bsf STATUS,RP0 ; Switch to bank 1 clrf EEADR ; Reset the EEPROM read address to 0 call read_EEPROM ; Read EEPROM (all in bank 1) movf EEDATA,w ; Get the first osc byte movwf osc_0 ; Save osc frequency call read_EEPROM ; Read EEPROM movf EEDATA,w ; movwf osc_1 ; Save it call read_EEPROM ; Read EEPROM movf EEDATA,w ; movwf osc_2 ; Save it call read_EEPROM ; Read EEPROM movf EEDATA,w ; movwf osc_3 ; Save it ; ; Set the power on frequency to the defined value. ; (They always follow the osc bytes) ; call read_EEPROM ; Read EEPROM movf EEDATA,w ; Get the first default freq byte movwf freq_0 ; Save it call read_EEPROM ; Read EEPROM movf EEDATA,w ; Get the next freq byte movwf freq_1 ; Save it call read_EEPROM ; Read EEPROM movf EEDATA,w ; Get the next freq byte movwf freq_2 ; Save it call read_EEPROM ; Read EEPROM movf EEDATA,w ; Get the last freq byte movwf freq_3 ; Save it bcf STATUS,RP0 ; Back to bank 0 ; ; 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/AD9851 in serial mode ; ; Get the power on encoder value. ; movf GPIO,w movwf ren_old ; Save it in ren_old movlw b'00110000' ; Get encoder mask (GPIO,4 and GPIO,5) andwf ren_old,f ; Get encoder bits and zero all other bits ; clrf saved ; clrf last_dir ; Clear the knob direction indicator ; ; Setup interupt on change pins bsf STATUS,RP0 ; Switch to bank 1 movlw b'00110000' ; Set GPIO,4 & 5 (encoder input) to interupt on change movwf IOC ; bcf STATUS,RP0 ; Back to bank 0 ; ; 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 variable fstep is added or subtracted from the current * ; * VFO frequency and stored in freq. * ; * 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/AD9851. This data is transferred * ; * to the AD9851 DDS chip by calling the subroutine send_dds_word. * ; * * ; * The frequency is saved to EPROM 2 seconds after the encoder * ; * stops turning. * ; * * ; * Input: None. * ; * * ; * Output: None. * ; * * ; ******************************************************************************* ; ; Put PIC to sleep until encoder moved for the first time main ; Start of 2s timer interrupt routine bsf STATUS,RP0 ; Switch BANK1 movlw b'00000111' ; Set TMRO prescaler 1:256 movwf OPTION_REG ; bcf STATUS,RP0 ; Switch to BANK0 ; movlw b'00000100' movwf INTCON ; Enable Timer0 interrupt, Disable all others bsf INTCON,GIE ; Enable general interrupts bsf INTCON,T0IE ; Enable Timer0 interrupt ; movlw 39 movwf TMR0 ; Timer0 starting point, writing this will start timer after two ins. cyc. movlw 18 ; Overflow counter for 2 second movwf CNT1 ; poll call poll_encoder ; Check for knob movement (wait there!) ; Return here when encoder change detected ; Based on the knob direction, either add or subtract the increment, ; then update DDS. ; stepdir btfsc last_dir,0 ; Is the knob going up? goto up ; Yes, then add the increment down call sub_step ; Subtract fstep from freq goto update ; Update DDS up call add_step ; Add fstep to freq call check_add ; Make sure we did not exceed the maximum update call calc_dds_word ; Calculate the control word for the DDS chip call send_dds_word ; Send the control word to the DDS chip ; goto main ; Continue main loop ; ; ******************************************************************************* ; * * ; * 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,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,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,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,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,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,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,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,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,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,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 ; ; ******************************************************************************* ; * * ; * Function: sub_step * ; * * ; * Purpose: Subtract the increment step from freq. * ; * * ; * Input: The values in fstep and freq_3..0. * ; * * ; * Output: None * ; * * ; * Revisions: Modified for limited range VFO. 29-9-13 VK5TM * ; * * ; ******************************************************************************* ; sub_step ; call invert_fstep ; Invert fstep_3..0 to perform the subtraction call add_step ; Add the complement to do the subtraction ; ; ******************************************************************************* ; * * ; * Function: low_limit_chk * ; * * ; * Purpose: Test the new frequency to see if it is above the lower band * ; * limit. If not, the frequency is set to the band lower limit. * ; * * ; * Input: Freq_0...3 and Low_limit_0...3 * ; * * ; * Output: Original frequency if above low limit or low_limit_0..3 in * ; * Freq_0...3 * ; * * ; ******************************************************************************* ; low_limit_chk ; Check the most significant byte. ; btfsc freq_3,7 goto set_low ; Yes, set to lower frequency limit movf freq_3,w subwf low_limit_3,w btfss STATUS,C ; Are we at the limit for the byte? goto limit_exit ; No, above. btfss STATUS,Z ; Are the bytes equal? goto set_low ; No, so vfo_X_3 > limit_3. ; ; Check the second most significant byte when MSB equals limit_3 ; movf freq_2,w subwf low_limit_2,w btfss STATUS,C ; Are we at the limit for the byte? goto limit_exit ; No, above. Check next. btfss STATUS,Z ; Might they be equal? goto set_low ; Nope, so vfo_X_2 > limit_2 ; ; Check the third most significant byte. ; movf freq_1,w subwf low_limit_1,w btfss STATUS,C ; Are we at the limit for the byte? goto limit_exit ; No, above. Checks are done. btfss STATUS,Z ; Check if the bytes are equal goto set_low ; No, so vfo_X_1 > limit_1 ; ; Check the least significant byte. ; movf freq_0,w subwf low_limit_0,w btfss STATUS,C ; Are we at the limit for the byte? goto limit_exit ; No, above. Checks are done. ; ; The frequency is below the band lower limit. Set frequency to the ; band starting frequency. set_low ; movf low_limit_0,w movwf freq_0 movf low_limit_1,w movwf freq_1 movf low_limit_2,w movwf freq_2 movf low_limit_3,w movwf freq_3 ; limit_exit call invert_fstep ; Put fstep back to original value return ; Return to caller ; ; ******************************************************************************* ; * * ; * Function: invert_fstep * ; * * ; * Purpose: Support function for sub_step and calibrate. This function * ; * negates the value of fstep_3..0 to assist in the frequency * ; * decrement. This operation is performed twice by sub_step, and * ; * is also used by calibrate. * ; * * ; * Input: fstep_3, fstep_2, fstep_1, fstep_0 * ; * * ; * Output: fstep_3..0 contain the 2's complement of their original value * ; * * ; ******************************************************************************* ; invert_fstep ; Invert the bits in comf fstep_0,f ; fstep_0 comf fstep_1,f ; fstep_1 comf fstep_2,f ; fstep_2 comf fstep_3,f ; fstep_3 incfsz fstep_0,f ; Now incremnt fstep_0 to get 2's complement goto invert_done ; If fstep_0 > 0, then done ; Else, there was a carry out of fstep_0 incfsz fstep_1,f ; Add 1 to fstep_1 goto invert_done ; If fstep_1 > 0, then done ; Else, there was a carry out of fstep_1 incfsz fstep_2,f ; Add 1 to fstep_2 goto invert_done ; if fstep_2 > 0, then done ; Else, there was a carry out of fstep_2 incf fstep_3,f ; Increment the high byte invert_done return ; Back to caller ; ; ******************************************************************************* ; * * ; * Purpose: This routine does the following: * ; * * ; * Reads the encoder bits until a change is detected, then * ; * determines the direction the knob was moved. * ; * * ; * Input: Knob input read from GPIO * ; * 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, 1 = up) * ; * * ; ******************************************************************************* ; poll_encoder ; btfsc saved,0 ; Test if in calibrate mode - ignore interrupt flags goto read_encoder btfsc saved,2 ; Test interrupt flag, jump over if not set call update_EEPROM ; Call to save freq when changed and timer timed out ; read_encoder ; btfsc GPIO,3 goto TX_rout ; va routine TX btfsc saved,3 ;Bit qui indique si -600Hz déjà fait goto RX_rout ; va routine remise qrg RX : +600Hz retr movf GPIO,w andlw b'00110000' movwf ren_read ; Save it ; ;!!!--- DETENT_ENCODER --- ADDED CODE FOR MECHANICAL ENCODER ----- !!! call wait_1ms ; debounce time movf GPIO,w andlw b'00110000' xorwf ren_read,w ; Compare with previous value btfss STATUS,Z ; Are they equal? goto read_encoder ; Poll again if not ;!!! ------------------------------------------------------------- !!! ; movlw b'00110000' andwf ren_read,w ; Isolate encoder bits into W movwf ren_new ; Save new value xorwf ren_old,w ; Has it changed? btfsc STATUS,Z ; Check zero-flag (zero if no change) goto poll_encoder ; No change, keep looking until it changes ; Zero-flag is not set, so continue on ; ; It changed. Now determine which direction the encoder turned. ;============================================================================= ; Encoder bits are on GPIO,4 and GPIO,5 - the bits 4 & 5 of ren_new ; A and B are "gray code" - 90 degrees out of phase (quadrature) ; ___ ___ ; | | | | ; A ____| |___| |___ ; ___ ___ ; | | | | ; B ___| |___| |___ ; ^ ^ ^ ^ ^ ^ ^ ^ ; a b c d a b c d ; ; A B ; At point a: 0 0 ; At point b: 1 0 ; At point c: 1 1 ; At point d: 0 1 ; ; Going UP, the sequence is a,b,c,d,a,b,c,d, etc. so the sequence is: ; 00, 10, 11, 01, 00, 10, 11, 01, etc. ; ; Going DOWN, the sequence is d,c,b,a,d,c,b,a, etc. so the sequence is: ; 01, 11, 10, 00, 01, 11, 10, 00, etc. ; ; To determine if the sequence is UP or DOWN: ; 1) Take the "Right-Bit" of any pair. ; 2) XOR it with the "Left-Bit" of the next pair in the sequence. ; 3) If the result is 1 it is UP ; If the result is 0 it is DOWN ; ; The direction flag is 0 (DOWN) or 1 (UP) ;============================================================================= ; bcf STATUS,C ; Clear the carry bit to prepare for rotate rlf ren_old,f ; Rotate old bits left to align "Right-Bit" movf ren_new,w ; Set up new bits in W xorwf ren_old,f ; XOR old (left shifted) with new bits movf ren_old,w ; Put XOR results into W also clrf last_dir ; Clear last_dir (default is DN) btfsc ren_old,5 goto up2 ; No, exit3 ; ;!!! --- DETENT_ENCODER --- ADDED CODE FOR MECHANICAL ENCODER ----- !!! movf ren_new,w ; Get the current encoder bits movwf ren_old ; Save them in ren_old for the next time decf enc_counter,f ; decrement the inter-detent counter btfsc enc_counter,0 ; Check if bit 0 is cleared goto poll_encoder ; If not, then poll some more btfsc enc_counter,1 ; Else, check if bit 1 is cleared goto poll_encoder ; If not, then poll some more ; Else, assume the encoder is on a detent ;!!! -------------------------------------------------------------- !!! goto exit3 ; up2 bsf last_dir,0 ; Set "UP" bit. ; ;!!! --- DETENT_ENCODER --- ADDED CODE FOR MECHANICAL ENCODER ----- !!! movf ren_new,w ; Get the current encoder bits movwf ren_old ; Save them in ren_old for the next time incf enc_counter,f ; Increment the encoder counter btfsc enc_counter,0 ; Return only on modulo 4 counts goto poll_encoder ; btfsc enc_counter,1 goto poll_encoder ; Loop again if not modulo 4 ;!!! -------------------------------------------------------------- !!! ; 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 ; ; ******************************************************************************* ; * * ; * Purpose: This routine is entered at start up if the calibrate pads are * ; * shorted at power-on time. * ; * * ; * The DDS chip is programmed to produce a frequency, based on the * ; * osc value stored in the EEPROM and the calibration frequency as * ; * at the head of the code. As long as the pads are shorted, the * ; * osc value is slowly altered to allow the output to be trimmed. * ; * Once the encoder is turned after the short is removed from the * ; * Cal pads, 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 bsf saved,0 ; set flag for poll encoder routine bcf INTCON,GIE ; Turn off interupts to be sure ; movlw cal_freq_0 ; Move Calibration frequency constants movwf freq_0 ; to freq_0...3 for Calibration routine. movlw cal_freq_1 ; movwf freq_1 ; movlw cal_freq_2 ; movwf freq_2 ; movlw cal_freq_3 ; movwf freq_3 ; ; ; Read the starting reference oscillator value from EEPROM. ; bsf STATUS,RP0 ; Switch to bank 1 clrf EEADR ; Reset the EEPROM read address call read_EEPROM ; Read first byte from EEPROM (all in bank 1) movf EEDATA,w ; Get the first osc byte movwf osc_0 ; Save osc frequency call read_EEPROM ; Read second byte from EEPROM movf EEDATA,w ; movwf osc_1 ; Save it call read_EEPROM ; Read third byte from EEPROM movf EEDATA,w ; movwf osc_2 ; Save it call read_EEPROM ; Read fourth byte from EEPROM movf EEDATA,w ; movwf osc_3 ; Save it bcf STATUS,RP0 ; Back to bank 0 for store ; 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. btfsc GPIO,3 ; Calibrate switch/jumper still set? goto cal_out ; No, go to exit and save values to EEPROM clrf fstep_3 ; Clear the three most significant clrf fstep_2 ; bytes of fstep clrf fstep_1 ; movlw 0x10 ; movwf fstep_0 ; Use small increment nop ; Wait a cycle btfsc last_dir,0 ; 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,C ; Was there a carry? goto add4 ; No, add the next bytes incfsz osc_1,f ; Ripple carry up to the next byte goto add4 ; No new carry, add the next bytes incfsz osc_2,f ; Ripple carry up to the next byte goto add4 ; No new carry, add the next bytes incf osc_3,f ; Ripple carry up to the highest byte add4 movf fstep_1,w ; Get the second byte increment addwf osc_1,f ; Add it to the second osc byte btfss STATUS,C ; Was there a carry? goto add5 ; No, add the third bytes incfsz osc_2,f ; Ripple carry up to the next byte goto add5 ; No new carry, add the third bytes incf osc_3,f ; Ripple carry up to the highest byte add5 movf fstep_2,w ; Get the third byte increment addwf osc_2,f ; Add it to the third osc byte btfss STATUS,C ; Was there a carry? goto add6 ; No, add the fourth bytes incf osc_3,f ; Ripple carry up to the highest byte add6 movf fstep_3,w ; Get the fourth byte increment addwf osc_3,f ; Add it to the fourth byte goto cal_loop ; Yes, stay in calibrate mode ; ; Write final value to EPROM ; cal_out bsf STATUS,RP0 ; Switch to bank 1 movf osc_0,w ; Get the first osc byte to record clrf EEADR ; osc bytes start at EEPROM address 0 movwf EEDATA ; Put byte in EEPROM write location call write_EEPROM ; movf osc_1,w ; Get the second byte to record movwf EEDATA ; Put byte in EEPROM write location call write_EEPROM ; movf osc_2,w ; Get the third byte to record movwf EEDATA ; Put byte in EEPROM write location call write_EEPROM ; movf osc_3,w ; Get the fourth byte to record movwf EEDATA ; Put byte in EEPROM write location call write_EEPROM ; movlw 8 movwf EEADR ; Move to EEPROM write location bsf cal_flag,0 ; Set bit 0 of cal_flag => CAL done movfw cal_flag ; Move file to w for save to EEprom movwf EEDATA ; Put byte in EEprom write location call write_EEPROM ; bcf STATUS,RP0 ; Back to bank 0 bcf saved,0 ; clear flag used poll encoder routine return ; Return to the caller ; ; ******************************************************************************* ; * * ; * Purpose: This routine will save the current frequency in EEPROM. This * ; * frequency will then be used as the initial frequency upon start * ; * up. Frequency is automatically saved 2 seconds after encoder * ; * stops moving * ; * * ; * Input: The constants in freq_0...3 * ; * * ; * Output: None * ; * * ; ******************************************************************************* ; update_EEPROM bcf INTCON,GIE ; turn interrupts off bcf saved,2 bsf STATUS,RP0 ; Switch to bank 1 movlw 4 ; Default startup frequency address location movwf EEADR ; and set up for start of EEPROM writes movf freq_0,w ; Get the first freq byte to write movwf EEDATA ; First freq byte to EEPROM Write register call write_EEPROM ; Write it movf freq_1,w ; Get the second freq byte to write movwf EEDATA ; Second freq byte to EEPROM Write register call write_EEPROM ; Write it movf freq_2,w ; Get the third freq byte to write movwf EEDATA ; Third freq byte to EEPROM Write register call write_EEPROM ; Write it movf freq_3,w ; Get the fourth freq byte to write movwf EEDATA ; Fourth freq byte to EEPROM Write register call write_EEPROM ; Write it bcf STATUS,RP0 ; Back to bank 0 bsf INTCON,GIE return ; ; ; ******************************************************************************* ; * * ; * Required sequence to write to EEPROM * ; * Used by update_EEPROM and calibrate routines * ; * * ; ******************************************************************************* ; write_EEPROM bsf EECON1,WREN ; Set the EEPROM write enable bit ; Start of required sequence bcf INTCON,GIE ; Disable interrupts movlw 0x55 ; Write 0x55 and 0xAA to EECON2 movwf EECON2 ; control register, as required movlw 0xAA ; movwf EECON2 ; bsf EECON1,WR ; Set WR bit to begin write ; End of required sequence bit_check btfsc EECON1,WR ; Has the write completed? goto bit_check ; No, keep checking bsf EECON1,GIE ; Enable interrupts bcf EECON1,WREN ; Clear the EEPROM write enable bit incf EEADR,f ; Increment the EE write address return ; Return to the caller ; ; ******************************************************************************* ; * * ; * Purpose: Shift QRG -600Hz ; * * ; * NOTE: Test si déjà en TX. Si oui, on passe, si non, QRG-600Hz et flag saved3 * ; * * ; ******************************************************************************* TX_rout call wait_1ms btfsc saved,3 ;Est-on en mode TX ? goto retr ; bcf INTCON,GIE ; disable int ==================================================================== movlw 0x00 ;On place le shift 600HZ à la place de 10Hz movwf fstep_0 movlw 0x02 movwf fstep_1 movlw 0x00 movwf fstep_2 movlw 0x00 movwf fstep_3 call sub_step ; QRG TX call calc_dds_word ; Calculate the control word for the DDS chip call send_dds_word ; Send the control word to the DDS chip movlw 0x0A ;On replace le shift 10Hz movwf fstep_0 movlw 0x00 movwf fstep_1 movlw 0x00 movwf fstep_2 movlw 0x00 movwf fstep_3 bsf saved,3 ; Flag mode TX active goto retr ; ******************************************************************************* ; * * ; * Purpose: Shift QRG +600Hz ; * * ; * NOTE: plus en TX, on annule le shift TX ; ******************************************************************************* RX_rout ; bcf INTCON,GIE ; disable int ==================================================================== movlw 0x00 ;On place le shift 600HZ à la place de 10Hz movwf fstep_0 movlw 0x02 movwf fstep_1 movlw 0x00 movwf fstep_2 movlw 0x00 movwf fstep_3 call add_step ;On revient à la QRG RX call calc_dds_word ; Calculate the control word for the DDS chip call send_dds_word ; Send the control word to the DDS chip movlw 0x0A ;On replace le shift 10Hz movwf fstep_0 movlw 0x00 movwf fstep_1 movlw 0x00 movwf fstep_2 movlw 0x00 movwf fstep_3 bcf saved,3 ; flag RX active ; bsf INTCON,GIE ; Enable int ======================================================================= goto retr ; ******************************************************************************* ; * * ; * Purpose: Read a byte of EEPROM data at address EEADR into EEDATA. * ; * * ; * Input: The address EEADR. * ; * * ; * Output: The value in EEDATA. * ; * * ; * NOTE: All in BANK 1 * ; * * ; ******************************************************************************* ; read_EEPROM ; bsf EECON1,RD ; Request the read movf EEDATA,W ; Get the data incf EEADR,f ; Increment the read address 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 AD9851_3 ... AD9851_0. * ; * * ; ******************************************************************************* ; calc_dds_word ; clrf AD9851_0 ; Clear the AD9850/AD9851 control word bytes clrf AD9851_1 ; clrf AD9851_2 ; clrf AD9851_3 ; clrf AD9851_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,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 ; Get the "normal" freq_0 term addwf AD9851_1,f ; Add it in to total btfss STATUS,C ; Does this addition result in a carry? goto add7 ; No, continue with next freq term incfsz AD9851_2,f ; Yes, add one and check for another carry goto add7 ; No, continue with next freq term incfsz AD9851_3,f ; Yes, add one and check for another carry goto add7 ; No, continue with next freq term incf AD9851_4,f ; Yes, add one and continue add7 movf freq_1,w ; Get the "normal" freq_0 term addwf AD9851_2,f ; Add freq term to total in correct position btfss STATUS,C ; Does this addition result in a carry? goto add8 ; No, continue with next freq term incfsz AD9851_3,f ; Yes, add one and check for another carry goto add8 ; No, continue with next freq term incf AD9851_4,f ; Yes, add one and continue add8 movf freq_2,w ; Get the "normal" freq_2 term addwf AD9851_3,f ; Add freq term to total in correct position btfss STATUS,C ; Does this addition result in a carry? goto add9 ; No, continue with next freq term incf AD9851_4,f ; Yes, add one and continue add9 movf freq_3,w ; Get the "normal" freq_3 term addwf AD9851_4,f ; Add freq term to total in correct position noAdd rrf AD9851_4,f ; Shift next multiplier bit into position rrf AD9851_3,f ; Rotate bits to right from byte to byte rrf AD9851_2,f ; rrf AD9851_1,f ; rrf AD9851_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 #ifdef AD9850 movlw 0x00 ; No clock multiplier (AD9850) #endif #ifdef AD9851 movlw 0x01 ; Turn on 6x clock multiplier (AD9851) #endif movwf AD9851_4 ; Last byte to be sent ; Mult answer is in bytes _3 .. _0 return ; Done. ; ; ******************************************************************************* ; * * ; * Purpose: This routine sends the AD9850/AD9851 control word to the DDS * ; * using a serial data transfer. * ; * * ; * Input: AD9851_4 ... AD9851_0 * ; * * ; * Output: The DDS chip register is updated. * ; * * ; ******************************************************************************* ; send_dds_word movlw AD9851_0 ; Point FSR at Least Significant Byte movwf FSR ; next_byte movf INDF,w ; movwf byte2send ; movlw 0x08 ; Set counter to 8 movwf bit_count ; next_bit rrf byte2send,f ; Test if next bit is 1 or 0 btfss STATUS,C ; Was it zero? goto send0 ; Yes, send zero bsf DDS_dat ; No, send one bsf DDS_clk ; Toggle write clock bcf DDS_clk ; goto break ; send0 bcf DDS_dat ; Send zero bsf DDS_clk ; Toggle write clock bcf DDS_clk ; break decfsz bit_count,f ; Has the whole byte been sent? goto next_bit ; No, keep going. incf FSR,f ; Start the next byte unless finished movlw AD9851_4+1 ; Next byte (past the end) subwf FSR,w ; btfss STATUS,C ; goto next_byte ; bsf DDS_load ; Send load signal to the AD9850/AD9851 bcf DDS_load ; bcf DDS_dat ; bcf DDS_clk ; return ; ; ; ******************************************************************************* ; * * ; * Purpose: Wait for 1 millisecond. * ; * * ; * * ; * Input: None * ; * * ; * Output: None (NOTE VK5TM Jan 2014 - my calculations make this 1.547mS * ; * * ; ******************************************************************************* ; wait_1ms movlw 0x2 movwf timer1 ; 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 ;------------------------------------------------------------------------------------------------