; ; ******************************************************************************* ; * * ; * VK5TM_Simple_DDS_Sweep.asm * ; * * ; * Copyright 2019 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 * ; * Bob Okas - W3CD (SK) * ; * Bruce Stough - AA0ED * ; * Craig Johnson - AA0ZZ * ; * * ; ******************************************************************************* ; ; ******************************************************************************* ; * * ; * This code uses a 12F1840 and a DDS module to sweep a range of * ; * frequencies. * ; * * ; * Features: * ; * Suitable for AD9850 or AD9851 DDS chips * ; * Upper and lower frequency limit * ; * * ; ******************************************************************************* ; ; ******************************************************************************* ; * * ; * Target Controller - PIC12F1840 * ; * ----\/---- * ; * VCC |1 8| GND * ; * CAL/STEP----RA5 |2 7| RA0---DDS DATA * ; * Switch A----RA4 |3 6| RA1---DDS FU_UP * ; * Switch B----RA3 |4 5| RA2---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 12F1840 radix dec errorlevel -302 ; Skip out of bank nuisance messages ; ******************************************************************************* ; * Configuration fuse information for 12F629 * ; ******************************************************************************* include P12F1840.INC ; __CONFIG _CONFIG1, _FOSC_INTOSC &_WDTE_OFF &_PWRTE_OFF &_MCLRE_OFF &_CP_OFF &_BOREN_OFF &_CLKOUTEN_OFF &_IESO_OFF &_FCMEN_OFF __CONFIG _CONFIG2, _WRT_OFF &_PLLEN_OFF &_STVREN_OFF &_BORV_LO &_LVP_OFF ; ; ******************************************************************************* ; * General equates. These may be changed to accommodate the reference * ; * clock frequency, the desired upper frequency limit and the desired * ; * start frequency. * ; ******************************************************************************* ; #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 0x01 ; Most significant byte for 30MHz limit_2 equ 0x2D ; Next byte limit_1 equ 0xC6 ; Next byte limit_0 equ 0xC0 ; Least significant byte ; ; Low_limit_3..0 contains the start frequency limit as a 32 bit integer ; limit_low_3 equ 0x00 ; Most significant byte for 50kHz limit_low_2 equ 0x00 ; Next byte limit_low_1 equ 0xC3 ; Next byte limit_low_0 equ 0x50 ; 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 0xF000 ; 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 ; ; ******************************************************************************* ; * Assign names to IO pins. * ; ******************************************************************************* ; #DEFINE DDS_clk PORTA,2 ; AD9850/AD9851 write clock #DEFINE DDS_dat PORTA,0 ; AD9850/AD9851 serial data input #DEFINE DDS_load PORTA,1 ; Update pin on AD9850/AD9851 ; ; ******************************************************************************* ; * Allocate variables in general purpose register space * ; ******************************************************************************* ; CBLOCK 0x20 ; Start Data Block AD9851_0 ; AD9850/AD9851 control word AD9851_1 ; (5 bytes) AD9851_2 ; DO NOT CHANGE THE ORDER OF THESE REGISTERS AD9851_3 ; AD9851_4 ; fstep_0 ; Frequency inc/dec fstep_1 ; (4 bytes) fstep_2 fstep_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 freq_0 ; Frequency (hex) freq_1 ; (4 bytes) freq_2 ; freq_3 ; ENDC ; End of Data Block CBLOCK 0x70 osc_0 ; Current oscillator osc_1 ; (4 bytes) osc_2 osc_3 ENDC ; End of Data Block ; ******************************************************************************* ; * The 12F1840 resets to 0x00. * ; * The Interrupt vector is at 0x04. * ; ******************************************************************************* ; ORG 0x0000 goto start ; Jump to main program ; ; ******************************************************************************* ; * * ; * 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 * ; * sweep the frequency. * ; * * ; * Input: The start up frequency is defined in the limit_low_0...3 * ; * definitions above and relies on the reference oscillator * ; * constant defined in ref_osc_3...ref_osc_0. * ; * * ; * Output: Normal sweep operation. * ; * * ; ******************************************************************************* ; start BANKSEL OSCCON ; movlw b'01101000' ; 4MHz PLL off ; movlw b'01110000' ; 8MHz ; movlw b'01111000' ; 16MHz movlw b'11110000' ; 32MHz PLL on movwf OSCCON ; configure oscillator BANKSEL PORTA ; CLRF PORTA ; Init PORTA BANKSEL LATA ; Data Latch CLRF LATA ; BANKSEL ANSELA ; CLRF ANSELA ; digital I/O BANKSEL OPTION_REG ; movlw b'10100111' ; GPIO pull-ups disabled, TMR0 clock source internal ; clock, prescaler to TMR0, set TMR0 prescaler 1:256 movwf OPTION_REG ; BANKSEL TRISA ; movlw b'00111000' ; GPIO 3,4,5 inputs 0,1,2 outputs movwf TRISA ; BANKSEL PORTA ; clrf INTCON ; clear INTCON ; ; ******************************************************************************* ; ; 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 ; ; START FREQUENCY 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 ; ; ******************************************************************************* ; * * ; * Get the reference oscillator constant from the EEPROM. * ; * * ; ******************************************************************************* read_EEocs BANKSEL EEADRL ; Switch to EEPROM bank clrf EEADRL ; Reset the EEPROM read address to 0 call read_EEPROM ; Read EEPROM (all in bank 1) movf EEDATL,w ; Get the first osc byte movwf osc_0 ; Save osc frequency call read_EEPROM ; Read EEPROM movf EEDATL,w ; movwf osc_1 ; Save it call read_EEPROM ; Read EEPROM movf EEDATL,w ; movwf osc_2 ; Save it call read_EEPROM ; Read EEPROM movf EEDATL,w ; movwf osc_3 ; Save it movlb 0 ; Back to bank 0 ; ; Set the start frequency to the defined value. ; movfw low_limit_0 movwf freq_0 ; movfw low_limit_1 movwf freq_1 ; movfw low_limit_2 movwf freq_2 ; movfw low_limit_3 movwf freq_3 ; clrf fstep_3 ; clrf fstep_2 ; clrf fstep_1 ; clrf fstep_0 ; ; ; Send start frequency to the DDS chip. ; call calc_dds_word ; Convert to delta value call send_dds_word ; Send the start frequency to the ; AD9850/AD9851 in serial mode ; step_5khz movlw 0x88 ; 5kHz steps movwf fstep_0 ; movlw 0x13 ; movwf fstep_1 ; step call add_step ; Add fstep to freq call check_add ; Make sure we did not exceed the maximum 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 step ; Continue main loop ; ; ******************************************************************************* ; * * ; * Purpose: This routine adds the 32 bit value of fstep to the 32 bit * ; * value in freq. * ; * * ; * 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. * ; * * ; ******************************************************************************* ; 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 start frequency. * ; * * ; ******************************************************************************* ; 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, exit. Checks are done. ; LOAD MIN FREQUENCY set_max movfw low_limit_0 ; Get least significant limit movwf freq_0 ; Set it in freq movfw low_limit_1 ; Get the next byte limit movwf freq_1 ; Set it in freq_1 movfw low_limit_2 ; Get the next byte limit movwf freq_2 ; Set it in freq_2 movfw low_limit_3 ; Get the most significant limit movwf freq_3 ; Set it in freq_3 exit1 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. * ; * * ; * NOTE: All in BANK 1 * ; * * ; ******************************************************************************* ; read_EEPROM bsf EECON1,RD ; Request the read movf EEDATL,W ; Get the data incf EEADRL,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 LOW AD9851_0 ; Point FSR at Least Significant Byte movwf FSR1L movlw HIGH AD9851_0 movwf FSR1H next_byte moviw 0[FSR1] 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 nop bsf DDS_clk ; Toggle write clock nop bcf DDS_clk ; goto break ; send0 bcf DDS_dat ; Send zero nop bsf DDS_clk ; Toggle write clock nop bcf DDS_clk ; break decfsz bit_count,f ; Has the whole byte been sent? goto next_bit ; No, keep going. incf FSR1L,f ; Start the next byte unless finished movlw AD9851_4+1 ; Next byte (past the end) subwf FSR1L,w ; btfss STATUS,C ; goto next_byte ; bsf DDS_load ; Send load signal to the AD9850/AD9851 nop bcf DDS_load ; nop bcf DDS_dat ; nop bcf DDS_clk ; return ; ; END ;--------------------------------------------------------------------------------