; ----- SELECT ONE OF THESE DDS TYPES AND TURN THE OTHER OFF !! --------- ;#define AD9850 ; Using the AD9850 (DDS-30) #define AD9851 ; Using the AD9851 (DDS-60) ; ----- SELECT 16-CHAR OR 8-CHAR LCD BY ENABLING ONE OF THESE --------- #DEFINE LCDCHAR 16 ; Turn on code for 16-character LCD ;#DEFINE LCDCHAR 8 ; Turn on code for 8-character LCD ; **************************************************************************** ; * PICELgen - Signal Generator (VFO) with AD9850/AD9851 DDS on PIC-EL * ; * Version 5.1 * ; * December 29, 2005 * ; * * ; **************************************************************************** ; Description: ; Originally, this was the control program for a DDS VFO built with an AD9850 ; DDS chip, a shaft encoder, two push button switches and an Liquid crystal ; display. This version supports the newer DDS - the AD9851 as well as the ; AD9850. Simply select the DDS that you are using at the top of this file. ; ; Features: ; VARIABLE RATE TUNING based on the speed at which the encoder is turned. ; Pressing a pushbutton switch (PIC-EL PB_1) will change the step size from ; 1Hz to 1kHz. ; ; BAND MEMORIES a pushbutton switch (PIC-EL PB_2) allows the frequency to ; be cycled around the HF ham bands. ; ; CALIBRATE MODE is entered if a pushbutton switch (PIC-EL PB_1) is pressed ; during power-on. The display is set to 10 MHz and remains fixed, ; even as adjustments are being made. If pushbutton is held pressed, then ; turning the shaft encoder will increase or decrease the value "osc" used to ; calculate the DDS control word. The basic calibrate adjustment rate is very ; low (on the order of a few cycles per turn of the encoder). A somewhat ; faster adjustment speed is available by pressing the encoder shaft down ; while turning. ; An external frequency counter on the DDS output is required to observe this ; adjustment. To exit calibrate mode, release the pushbutton and turn the ; shaft encoder one more time. The calibrated value of "osc" will then be ; stored in EEPROM memory. ; ; MOVE UP 1 MHz - Press and hold PIC-EL pushbutton PB_2 and then press and ; then press and release PIC-EL pushbutton PB_1. ; ; MOVE DOWN 1 MHz - Press and hold PIC-EL pushbutton PB_1 and then press and ; then press and release PIC-EL pushbutton PB_2. ; ; SAVE NEW START-UP FREQUENCY - Select the frequency. Then press and hold ; PIC-EL pushbuttons PB_1 and PB_2 for 2 seconds. The frequency will be ; stored in EEPROM and will be used on next start-up. ; ;****************************************************************************** ; Original Author - Curtis W. Preuss - WB2V ; ; Modification History ; 8/19/98 - Version 1 - Initial Version by Curtis W. Preuss - WB2V ; 12/xx/98 - Version 2 - Converted to MPASM by Bruce Stough, AA0ED ; 4/21/99 - Version 3 - Fixed and modified by ; Bruce Stough, AAED (sbs1@visi.com) and ; Craig Johnson, AA0ZZ (cbjohns@cbjohns.com) ; ; 10/31/03 PICELgen1.0 Modify for PIC Elmer project by Craig Johnson, AA0ZZ ; 1) Change to use 1x8 LCD instead of 1x16 ; - Freq displayed as Hz (e.g. 14025000) ; - CAL freq displayed as Hz (e.g. 10000000) ; 2) Set up to support the NJQRP DDS Daughterboard ; - 50 MHz oscillator ; 3) Change PortB pin allocations to support PIC-EL ; - Change RB4-RB7 to RB0-RB3 ; - Change RB0 (DDS LOAD) to RB7 ; - Change RB1 (LCD_rs) to RB6 ; - Change RB2 (LCD_rw) to RB5 ; - Change RB3 (LCD_e) to RB4 ; - Change Busy_check routine to check correct bit ; - Change cmnd2LCD/data2LCD routine - swap nibbles ; 4) Change RA2 to always be an LOW output ; 5) Support pushbutton on RA3 instead of encoder ; shaft switch for bandswitch and calibrate ; 6) Support pushbutton on RA4 for fast tuning ; ; 1/3/04 PICELgen1.1 Add Title and Version on start-up ; 1/7/04 PICELgen1.2 Restructure main loop and change_band ; 2/2/04 PICELgen1.2a Fix confusing comment in the header re shaft sw. ; Fix comment in header regarding PB_2 for calib. ; 2/8/04 PICELgen1.3 Fix for reliable startup of DDS ; Add code for 1 MHz steps (up or down) ; Add code to save new start-up frequency ; 3/12/04 PICELgen1.4 Remove use of watchdog timer (temp sensitivity) ; Add code to support either 1x8 or 1x16 LCDs ; - Use #DEFINE to select the LCD type ; Fix calibrate routine so it stays in cal_loop ; 12/20/05 PICELgen4.0 Updated to use AD9851 instead of AD9850 ; 12/25/05 PICELgen5.0 Support BOTH AD9850 and AD9851 via complier option ; 12/29/05 PICELgen5.1 Fix bug. Speaker being left on, wasting 73mA ;***************************************************************************** ; ; Target Controller - PIC16F84A in PIC-EL board ; __________ ; PB_3-Speaker----RA2 |1 18| RA1---------ENCODER A ; PB_2-Bandswitch-RA3 |2 17| RA0---------ENCODER B ; PB_1-Tuning etc-RA4 |3 16| OSC1--------XTAL ; +5V-----------!MCLR |4 15| OSC2--------XTAL ; Ground----------Vss |5 14| VDD---------+5 V ; LCD11-----------RB0 |6 13| RB7---------DDS_LOAD ; LCD12-----------RB1 |7 12| RB6---------LCD_rs (LCD Pin 4) ; LCD13/DDS_CLK---RB2 |8 11| RB5---------LCD_rw (LCD Pin 5) ; LCD14/DDS_DATA--RB3 |9 10| RB4---------LCD_e (LCD Pin 6) ; ---------- ; ; **************************************************************************** ; * Device type and options. * ; **************************************************************************** ; processor PIC16F84A radix dec ; ; **************************************************************************** ; * Configuration fuse information: * ; **************************************************************************** _CP_ON EQU H'000F' _CP_OFF EQU H'3FFF' _PWRTE_ON EQU H'3FF7' _PWRTE_OFF EQU H'3FFF' _WDT_ON EQU H'3FFF' _WDT_OFF EQU H'3FFB' _LP_OSC EQU H'3FFC' _XT_OSC EQU H'3FFD' _HS_OSC EQU H'3FFE' _RC_OSC EQU H'3FFF' ; __config _CP_OFF & _PWRTE_ON & _WDT_OFF & _XT_OSC ; ; *************************************************************************** ; Info for power-up display MCODE_REV_0 equ '5' ; Current code version is 5.1 D MCODE_REV_1 equ '.' ; MCODE_REV_2 equ '1' ; MCODE_REV_3 equ '-' ; #ifdef AD9850 MCODE_REV_4 equ '3' ; MCODE_REV_5 equ '0' ; #endif #ifdef AD9851 MCODE_REV_4 equ '6' ; MCODE_REV_5 equ '0' ; #endif MCODE_REV_6 equ ' ' ; ; ; **************************************************************************** ; * 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 ; ; 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 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 ; ; 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 14. ; 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 100 MHz Oscillator ======= ref_osc_3 equ 0x2A ; Most significant osc byte ref_osc_2 equ 0xF3 ; Next byte ref_osc_1 equ 0x1D ; Next byte ref_osc_0 equ 0xC4 ; Least significant byte #endif #ifdef AD9851 ; For 180 MHz (30 MHz clock and 6x multiplier) D ref_osc_3 equ 0x17 ; Most significant osc byte D ref_osc_2 equ 0xDC ; Next byte D ref_osc_1 equ 0x65 ; Next byte D ref_osc_0 equ 0xDE ; Least significant byte D #endif ; 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. ; #ifdef AD9850 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 #endif #ifdef AD9851 limit_3 equ 0x03 ; Most significant byte for 60 MHz D limit_2 equ 0x93 ; Next byte D limit_1 equ 0x87 ; Next byte D limit_0 equ 0x00 ; Least significant byte D #endif ; ; Default contains the default startup frequency as a 32 bit integer. ; default_3 equ 0x00 ; Most significant byte for 14.025 MHz default_2 equ 0xD6 ; Next byte default_1 equ 0x01 ; Next byte default_0 equ 0x28 ; Least significant byte ; band_end equ 0x28 ; The offset to the last band table entry ; ; PB_flags bits PB1first equ 0 ; Bit set indicates PB1 pressed first ; EEstartup_adr equ 4 ; Location of startup frequency in EEPROM ; ; **************************************************************************** ; * Port and EEPROM Constants * ; **************************************************************************** ; PortA equ 0x05 PortB equ 0x06 TRISA equ 0x05 TRISB equ 0x06 EEdata equ 0x08 EEadr equ 0x09 WREN equ 0x02 WR equ 0x01 RD equ 0x00 ; ; **************************************************************************** ; * ID location information: * ; * (MPASM warns about DW here, don't worry) * ; **************************************************************************** ; ORG 0x2000 DATA 0x007F DATA 0x007F DATA 0x007F DATA 0x007F ; ; ; **************************************************************************** ; * Setup the initial constant, based on the frequency of the reference * ; * oscillator. This can be tweaked with the calibrate function. * ; **************************************************************************** ; ORG 0x2100 ; ref_osc bytes must be first 4 bytes of EEPROM 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 ; ; 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 ; ; **************************************************************************** ; * RAM page independent file registers: * ; **************************************************************************** ; INDF EQU 0x00 PCL EQU 0x02 STATUS EQU 0x03 FSR EQU 0x04 PCLATH EQU 0x0A INTCON EQU 0x0B ; ; ***************************************************************************** ; * Bit numbers for the STATUS file register: * ; ***************************************************************************** ; B_RP0 EQU 5 B_NTO EQU 4 B_NPD EQU 3 B_Z EQU 2 B_DC EQU 1 B_C EQU 0 ; ; **************************************************************************** ; * Assign names to IO pins. * ; **************************************************************************** ; ; B register bits: ; DDS_clk equ 0x02 ; AD9850/AD9851 write clock DDS_dat equ 0x03 ; AD9850/AD9851 serial data input LCD_busy equ 0x03 ; LCD busy bit LCD_e equ 0x04 ; 0=disable, 1=enable LCD_rw equ 0x05 ; 0=write, 1=read LCD_rs equ 0x06 ; 0=instruction, 1=data DDS_load equ 0x07 ; Update pin on AD9850/AD9851 ; ; A register bits: ; speaker equ 0x02 ; Speaker (always exit with a low output) pb_3 equ 0x02 ; PB also on this pin pb_2 equ 0x03 ; Bandswitch Pushbutton, pb_1 equ 0x04 ; Tuning-increment/Calibrate Pushbutton ; ; **************************************************************************** ; * Allocate variables in general purpose register space * ; **************************************************************************** ; CBLOCK 0x0c ; Start Data Block ; freq_0 ; Display frequency (hex) freq_1 ; (4 bytes) freq_2 freq_3 BCD_0 ; Display frequency (BCD) BCD_1 ; (5 bytes) BCD_2 BCD_3 BCD_4 AD9851_0 ; AD9850/AD9851 control word ;D AD9851_1 ; (5 bytes) ;D AD9851_2 ;D AD9851_3 ;D AD9851_4 ;D fstep_0 ; Frequency inc/dec fstep_1 ; (4 bytes) fstep_2 fstep_3 BCD_count ; Used in bin2BCD routine BCD_temp ; " mult_count ; Used in calc_dds_word bit_count ; " byte2send ; osc_0 ; Current oscillator osc_1 ; (4 bytes) osc_2 osc_3 osc_temp_0 ; Oscillator frequency osc_temp_1 ; (4 bytes) osc_temp_2 osc_temp_3 LCD_char ; Character being sent to the LCD LCD_read ; Character read from the LCD timer1 ; Used in delay routines timer2 ; " ren_timer_0 ; For variable rate tuning ren_timer_1 ; (2 bytes) ren_new ; New value of encoder pins A and B ren_old ; Old value of encoder pins A and B ren_read ; Encoder pins A and B and switch pin last_dir ; Indicates last direction of encoder next_dir ; Indicates expected direction count ; loop counter (gets reused) band ; Used to index a table of frequencies rs_value ; The LCD rs line flag value PB_flags ; Pushbutton flags PB_wait_count ; Wait for 2 seconds ; ENDC ; End of Data Block ; ; **************************************************************************** ; * The 16F84 resets to 0x00. * ; * The Interrupt vector is at 0x04. (Unused) * ; **************************************************************************** ; ORG 0x0000 reset_entry goto start ; Jump around the band table to main program ; ; **************************************************************************** ; * This is the band table. Each entry is four instructions long, with each * ; * group of four literals representing the frequency as a 32 bit integer. * ; * New entries can be added to the end of the table or between existing * ; * entries. The constant band_end must be incremented by 4 for each entry * ; * added. * ; * * ; * This table is placed near the top of the program to allow as large a * ; * a table as possible to be indexed with the eight bit value in W. * ; * * ; **************************************************************************** ; band_table addwf PCL,f ; retlw 0x00 ; 0 Hz retlw 0x00 ; retlw 0x00 ; retlw 0x00 ; retlw 0x00 ; 160 meters retlw 0x1B ; retlw 0x77 ; retlw 0x40 ; retlw 0x00 ; 80 meters retlw 0x35 ; retlw 0x67 ; retlw 0xE0 ; retlw 0x00 ; 40 meters retlw 0x6A ; retlw 0xCF ; retlw 0xC0 ; retlw 0x00 ; 30 meters retlw 0x9A ; retlw 0x1D ; retlw 0x20 ; retlw 0x00 ; 20 meters retlw 0xD5 ; retlw 0x9F ; retlw 0x80 ; retlw 0x01 ; 17 meters retlw 0x13 ; retlw 0xB2 ; retlw 0x20 ; retlw 0x01 ; 15 meters retlw 0x40 ; retlw 0x6F ; retlw 0x40 ; retlw 0x01 ; 12 meters retlw 0x7B ; retlw 0xCA ; retlw 0x90 ; retlw 0x01 ; 10 meters retlw 0xAB ; retlw 0x3F ; retlw 0x00 ; retlw 0x01 ; 30 MHz retlw 0xC9 ; retlw 0xC3 ; retlw 0x80 ; ; ; ***************************************************************************** ; * * ; * Purpose: This is the start of the program. It initializes the LCD and * ; * detects whether to enter calibrate mode. If so, it calls the * ; * Calibrate routine. Otherwise, it sets the power-on frequency * ; * and enters the loop to poll the encoder. * ; * * ; * Input: The start up frequency is defined in the default_3 ... * ; * definitions above, and relies on the reference oscillator * ; * constant defined in ref_osc_3 ... ref_osc_0. * ; * * ; * Output: Normal VFO operation. * ; * * ; ***************************************************************************** ; start clrf INTCON ; No interrupts for now bsf STATUS,B_RP0 ; Switch to bank 1 bcf 0x01,7 ; Enable weak pullups movlw 0xFB ; Tristate PortA (all Inputs except RA2) movwf TRISA ; clrf TRISB ; Set port B to all outputs bcf STATUS,B_RP0 ; Switch back to bank 0 bcf PortA,speaker ; Set speaker output low (don't use here) call init_LCD ; Initialize the LCD call display_version ; Display title and version ; ; Enter Calibrate Mode if push button is pressed while turning the ; power on. ; btfsc PortA,pb_1 ; Tuning-increment/Cal pushbutton 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. ; (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 ; ; Display the power on frequency. ; call bin2BCD ; Convert it to BCD call show_freq ; Display it ; ; Send power on frequency to the DDS chip. ; call calc_dds_word ; Convert to delta value call send_dds_word ; Send the power-on frequency to the ; AD9850/AD9851 in serial mode call send_dds_word ; Send it twice. Sometimes needed for ; a clean start-up ; ; Get the power on encoder value. ; movf PortA,w ; Read port A movwf ren_read ; Save it in ren_read movlw 0x03 ; Get encoder mask (RA0 and RA1) andwf ren_read,w ; Get encoder bits movwf ren_old ; Save in ren_old ; ; Initialize variables. ; clrf ren_timer_1 ; Initialize the encoder speed timer movlw 0x40 ; to movwf ren_timer_0 ; 0x0040 clrf last_dir ; Clear the knob direction indicator clrf band ; Clear the band indicator ; ; Fall into the Main Program Loop ; ; ***************************************************************************** ; * * ; * Purpose: This is the Main Program Loop. The program's main loop * ; * calls poll_encoder, which continuously polls the rotary shaft * ; * encoder. When the shaft encoder has changed, the direction * ; * it moved is determined and stored in last_dir. The subroutine * ; * then returns to main. * ; * * ; * If the tuning-increment pushbutton (PIC-EL PB_1) is not pressed,* ; * then the variable fstep is calculated based on the delay * ; * between shaft encoder changes. ren_timer contains the delay * ; * value determined by the poll_encoder subroutine. The variable * ; * fstep is added or subtracted from the current VFO frequency * ; * stored in freq. The contents of freq are then converted to a * ; * BCD number in subroutine bin2BCD. The subroutine show_freq is * ; * then called to display the result on the Liquid Crystal Display.* ; * Next, the subroutine calc_dds_word is used to calculate the DDS * ; * frequency control word from the values in freq and osc. * ; * The result is stored in AD9850/AD9851. This data is transferred* ; * to the AD9851 DDS chip by calling the subroutine send_dds_word. * ; * * ; * If the bandswitch pushbutton (PIC-EL PB_2) is pressed while * ; * turning the encoder then the freq is loaded with a constant * ; * stored in band_table. The variable band is used as an index * ; * into the table. Band is incremented or decremented based on * ; * the encoder direction. * ; * * ; * Input: None. * ; * * ; * Output: None. * ; * * ; ***************************************************************************** ; main call poll_encoder ; Check for knob movement (wait there!) ; Return here when encoder change detected btfsc ren_read,pb_2 ; Is bandswitch pushbutton pressed? goto step ; No, just step call change_band ; Yes, change_band goto main ; Continue main loop step ; ; ; Determine step size to use (1 Hz or 1 kHz). ; clrf fstep_3 ; Guess that we want 1 Hz steps by clrf fstep_2 ; setting fstep to one. clrf fstep_1 ; movlw 0x01 ; movwf fstep_0 ; btfsc ren_read,pb_1 ; Is the tuning-increment button pressed? goto go_step ; No, use the 1 Hz step movlw 0xE8 ; Yes, set the step value to 1 kHz movwf fstep_0 ; by setting fstep_0 to 0xE8 and movlw 0x03 ; fstep_1 to 0x03 movwf fstep_1 ; goto go_step ; Use the 1 kHz step ; ; Adjust the tuning step based on ren_timer. ren_timer is incremented ; by 8 from its initial value of 0x0040 each time the poll_encoder finds ; no change in the encoder input, until the high bit of ren_timer_1 ; becomes a one. The default fstep of 1 Hz is multiplied by two for ; each leading zero in ren_timer, up to a maximum of 9 times. (This is ; because ren_timer starts at 0x0040, only the first nine bits can be ; zero in a row). The faster the knob is turned, the lower the number ; in ren_timer will be, and the larger the step value will be. ; ; bump_step bcf STATUS,B_C ; Clear the carry flag rlf fstep_0,f ; Multiply the step by 2 by rotating left rlf fstep_1,f ; rlf fstep_2,f ; rlf fstep_3,f ; go_step rlf ren_timer_0,f ; Multiply the encoder timer by 2 rlf ren_timer_1,f ; btfss STATUS,B_C ; Has a one floated to the carry yet? goto bump_step ; No, then double the step size ; ; Based on the knob direction, either add or subtract the increment, ; then update the LCD and DDS. ; btfsc last_dir,1 ; Is the knob going up? goto up ; Yes, then add the increment down call sub_step ; Subtract fstep from freq goto update ; Update LCD and DDS up call add_step ; Add fstep to freq call check_add ; Make sure we did not exceed the maximum update call bin2BCD ; Convert the frequency to BCD call show_freq ; Display the frequency on the LCD call calc_dds_word ; Find the control word for the DDS chip call send_dds_word ; Send the control word to the DDS chip goto main ; Continue main loop ; ; ***************************************************************************** ; * * ; * Purpose: This routine increments through the band table each time the * ; * knob moves a notch, updating the LCD and DDS, until the band * ; * button is no longer pushed. * ; * * ; * Input: The value of the band push button and the encoder bits * ; * * ; * Output: Updated freq value, and new frequency on the LCD and DDS. * ; * * ; ***************************************************************************** ; change_band btfsc last_dir,1 ; Are we going up in the band list? goto band_up ; Yes, increment band address movlw 0x04 ; No, get 4 bytes to subtract subwf band,f ; Move down in band list movlw 0xFF-band_end ; Check to see if we have fallen off the addwf band,w ; bottom of the table. btfss STATUS,B_C ; Off the bottom? goto valid ; No, continue movlw band_end ; Yes, go to highest entry movwf band ; valid call get_band ; Get the new band frequency goto write ; Set the frequency and continue band_up movlw 0x04 ; Table entries are 4 bytes apart addwf band,f ; Increment the band pointer movlw 0xFF-band_end ; Check to see if we have gone over the addwf band,w ; top of the table. btfsc STATUS,B_C ; Did we go over the top of the table? clrf band ; Yes, go to the bottom entry call get_band ; Get the new band frequency write call bin2BCD ; Convert the frequency to BCD call show_freq ; Display the frequency on the LCD call calc_dds_word ; Find the control word for the DDS chip call send_dds_word ; Send the control word to the DDS chip return ; Return to main loop ; ; ***************************************************************************** ; * * ; * Purpose: This routine reads the frequency value of a band table entry * ; * pointed to by band and returns it in freq_3...freq_0. * ; * * ; * Input: band must contain the index of the desired band entry * 4 * ; * (with the entries numbered from zero). * ; * * ; * Output: The band frequency in freq. * ; * * ; ***************************************************************************** ; get_band movf band,w ; Get the index of the high byte call band_table ; Get the value into W movwf freq_3 ; Save it in freq_3 incf band,f ; Increment index to next byte movf band,w ; Get the index of the next byte call band_table ; Get the value into W movwf freq_2 ; Save it in freq_2 incf band,f ; Increment index to the next byte movf band,w ; Get the index to the next byte call band_table ; Get the value into W movwf freq_1 ; Save it in freq_1 incf band,f ; Increment index to the low byte movf band,w ; Get the index to the low byte call band_table ; Get the value into W movwf freq_0 ; Save it in freq_0 movlw 0x03 ; Get a constant three subwf band,f ; Restore original value of band return ; Return to the caller ; ; ***************************************************************************** ; * * ; * Purpose: Power on initialization of Liquid Crystal Display. The LCD * ; * controller chip must be equivalent to an Hitachi 44780. The * ; * LCD is assumed to be a 8x1 or a 16x1 display. * ; * * ; * Input: None * ; * * ; * Output: None * ; * * ; ***************************************************************************** ; init_LCD call wait_64ms ; Wait for LCD to power up ; Put 4-bit command in RB3..RB0 ; PIC's RB3..RB0 lines connect to LCD's DB7..DB4 (pins 14-11) movlw 0x03 ; LCD init instruction (First) movwf PortB ; Send to LCD via RB3..RB0 bsf PortB,LCD_e ; Set the LCD E line high, call wait_64ms ; wait a "long" time, bcf PortB,LCD_e ; and then Clear E movlw 0x03 ; LCD init instruction (Second) movwf PortB ; Send to LCD via RB3..RB0 bsf PortB,LCD_e ; Set E high, call wait_32ms ; wait a while, bcf PortB,LCD_e ; and then Clear E movlw 0x03 ; LCD init instruction (Third) movwf PortB ; Send to LCD via RB3..RB0 bsf PortB,LCD_e ; Set E high, call wait_32ms ; wait a while, bcf PortB,LCD_e ; and then Clear E movlw 0x02 ; 4-bit mode instruction movwf PortB ; Send to LCD via RB3..RB0 bsf PortB,LCD_e ; Set E high, call wait_16ms ; wait a while, bcf PortB,LCD_e ; and then Clear E movlw 0x28 ; 1/16 duty cycle, 5x8 matrix call cmnd2LCD ; Send command in w to LCD movlw 0x08 ; Display off, cursor and blink off call cmnd2LCD ; Send command to LCD movlw 0x01 ; Clear and reset cursor call cmnd2LCD ; Send command in w to LCD movlw 0x06 ; Set cursor to move right, no shift call cmnd2LCD ; Send command in w to LCD movlw 0x0C ; Display on, cursor and blink off call cmnd2LCD ; Send command in w to LCD return ; ; ; ****************************************************************************P ; * P ; * Purpose: Display version and other info on LCD for 2 seconds P ; * upon power-up P ; * P ; * Input: MCODE_REV_0 through MCODE_REV_4 set up P ; * P ; * Output: LCD displays debug info P ; * P ; ****************************************************************************P ; display_version #IF LCDCHAR == 8 movlw 0x80 ; Point LCD at digit 1 call cmnd2LCD ; movlw 'P' ; digit 1 call data2LCD ; movlw 'I' ; digit 2 call data2LCD ; movlw 'C' ; digit 3 call data2LCD ; movlw 'E' ; digit 4 call data2LCD ; movlw 'L' ; digit 5 call data2LCD ; movlw 'g' ; digit 6 call data2LCD ; movlw 'e' ; digit 7 call data2LCD ; movlw 'n' ; digit 8 call data2LCD ; call wait_a_sec ; Wait one second ; movlw 0x80 ; Point LCD at digit 1 call cmnd2LCD ; movlw 'v' ; digit 1 call data2LCD ; movlw MCODE_REV_0 ; Get mcode rev byte call data2LCD ; and display it (digit 2) movlw MCODE_REV_1 ; Get mcode rev byte call data2LCD ; and display it (digit 3) movlw MCODE_REV_2 ; Get mcode rev byte call data2LCD ; and display it (digit 4) movlw MCODE_REV_3 ; Get mcode rev byte call data2LCD ; and display it (digit 5) movlw MCODE_REV_4 ; Get mcode rev byte call data2LCD ; and display it (digit 6) movlw MCODE_REV_5 ; Get mcode rev byte call data2LCD ; and display it (digit 7) movlw MCODE_REV_6 ; Get mcode rev byte call data2LCD ; and display it (digit 8) call wait_a_sec ; Wait one second #ENDIF ; End of 8-character LCD code #IF LCDCHAR == 16 ; Start 16-character LCD code movlw 0x80 ; Point LCD at digit 1 call cmnd2LCD ; movlw 'P' ; digit 1 call data2LCD ; movlw 'I' ; digit 2 call data2LCD ; movlw 'C' ; digit 3 call data2LCD ; movlw 'E' ; digit 4 call data2LCD ; movlw 'L' ; digit 5 call data2LCD ; movlw 'g' ; digit 6 call data2LCD ; movlw 'e' ; digit 7 call data2LCD ; movlw 'n' ; digit 8 call data2LCD ; movlw 0xC0 ; Point LCD at digit 9 movwf LCD_char ; call cmnd2LCD ; Send command to LCD movlw ' ' ; Space in digit 9 call data2LCD ; movlw 'v' ; digit 10 call data2LCD ; movlw MCODE_REV_0 ; Get mcode rev byte call data2LCD ; and display it (digit 11) movlw MCODE_REV_1 ; Get mcode rev byte call data2LCD ; and display it (digit 12) movlw MCODE_REV_2 ; Get mcode rev byte call data2LCD ; and display it (digit 13) movlw MCODE_REV_3 ; Get mcode rev byte call data2LCD ; and display it (digit 14) movlw MCODE_REV_4 ; Get mcode rev byte call data2LCD ; and display it (digit 15) movlw MCODE_REV_5 ; Get mcode rev byte call data2LCD ; and display it (digit 16) call wait_a_sec ; Wait one second #ENDIF ; End of 16-character LCD ; return ; ; ***************************************************************************** ; * * ; * Purpose: This routine adds the 32 bit value of fstep to the 32 bit * ; * value in freq. When incrementing, the fstep value is a * ; * positive integer. When decrementing, fstep is the complement * ; * of the value being subtracted. * ; * * ; * Input: The 32 bit values in fstep and freq * ; * * ; * Output: The sum of fstep and freq is stored in freq. When incrementing * ; * this value may exceed the maximum. When decrementing, it may * ; * go negative. * ; * * ; ***************************************************************************** add_step movf fstep_0,w ; Get low byte of the increment addwf freq_0,f ; Add it to the low byte of freq btfss STATUS,B_C ; Any carry? goto add1 ; No, add next byte incfsz freq_1,f ; Ripple carry up to the next byte goto add1 ; No new carry, add next byte incfsz freq_2,f ; Ripple carry up to the next byte goto add1 ; No new carry, add next byte incf freq_3,f ; Ripple carry up to the highest byte add1 movf fstep_1,w ; Get the next increment byte addwf freq_1,f ; Add it to the next higher byte btfss STATUS,B_C ; Any carry? goto add2 ; No, add next byte incfsz freq_2,f ; Ripple carry up to the next byte goto add2 ; No new carry, add next byte incf freq_3,f ; Ripple carry up to the highest byte add2 movf fstep_2,w ; Get the next to most significant increment addwf freq_2,f ; Add it to the freq byte btfss STATUS,B_C ; Any carry? goto add3 ; No, add last byte incf freq_3,f ; Ripple carry up to the highest byte add3 movf fstep_3,w ; Get the most significant increment byte addwf freq_3,f ; Add it to the most significant freq return ; Return to the caller ; ; ***************************************************************************** ; * * ; * Purpose: Check if freq exceeds the upper limit. * ; * * ; * Input: The 32 bit values in freq * ; * * ; * Output: If freq is below the limit, it is unchanged. Otherwise, it is * ; * set to equal the upper limit. * ; * * ; ***************************************************************************** ; check_add ; ; Check the most significant byte. ; movlw 0xFF-limit_3 ; Get (FF - limit of high byte) addwf freq_3,w ; Add it to the current high byte btfsc STATUS,B_C ; Was high byte too large? goto set_max ; Yes, apply limit movlw limit_3 ; Get high limit value subwf freq_3,w ; Subtract the limit value btfss STATUS,B_C ; Are we at the limit for the byte? goto exit1 ; No, below. Checks are done. ; ; Check the second most significant byte. ; movlw 0xFF-limit_2 ; Get (FF - limit of next byte) addwf freq_2,w ; Add it to the current byte btfsc STATUS,B_C ; Is the current value too high? goto set_max ; Yes, apply the limit movlw limit_2 ; Second limit byte subwf freq_2,w ; Subtract limit value btfss STATUS,B_C ; Are we at the limit for the byte? goto exit1 ; No, below. Checks are done. ; ; Check the third most significant byte. ; movlw 0xFF-limit_1 ; Get (FF - limit of next byte) addwf freq_1,w ; Add it to the current byte btfsc STATUS,B_C ; Is the current value too high? goto set_max ; Yes, apply the limit movlw limit_1 ; Third limit byte subwf freq_1,w ; Subtract limit value btfss STATUS,B_C ; Are we at the limit for the byte? goto exit1 ; No, below. Checks are done. ; ; Check the least significant byte. ; movlw limit_0 ; Fourth limit byte subwf freq_0,w ; Subtract limit value btfss STATUS,B_C ; Are we at the limit for the byte? goto exit1 ; No, below. Checks are done. set_max movlw limit_0 ; Get least significant limit movwf freq_0 ; Set it in freq movlw limit_1 ; Get the next byte limit movwf freq_1 ; Set it in freq_1 movlw limit_2 ; Get the next byte limit movwf freq_2 ; Set it in freq_2 movlw limit_3 ; Get the most significant limit movwf freq_3 ; Set it in freq_3 exit1 return ; Return to the caller ; ; ***************************************************************************** ; * * ; * Purpose: Subtract the increment step from freq, checking that it does * ; * not go below zero. * ; * * ; * Input: The values in fstep and freq. * ; * * ; * Output: The updated value in freq. * ; * * ; ***************************************************************************** ; sub_step comf fstep_0,f ; Subtraction of fstep from comf fstep_1,f ; freq is done by adding the comf fstep_2,f ; twos compliment of fstep to comf fstep_3,f ; freq. incfsz fstep_0,f ; Increment last byte goto comp_done ; Non-zero, continue incfsz fstep_1,f ; Increment next byte goto comp_done ; Non-zero, continue incfsz fstep_2,f ; Increment next byte goto comp_done ; Non-zero, continue incf fstep_3,f ; Increment the high byte comp_done call add_step ; Add the compliment to do the subtraction ; ; If the frequency has gone negative, clear it to zero. ; btfss freq_3,7 ; Is high order frequency byte "negative"? goto exit2 ; No, keep going set_min clrf freq_0 ; Yes, set the frequency to zero clrf freq_1 ; clrf freq_2 ; clrf freq_3 ; exit2 return ; Return to the caller ; ; ***************************************************************************** ; * * ; * Purpose: This routine does the following: * ; * 1. Records how long it took for the knob to move a notch * ; * in ren_timer. * ; * 2. Clears the watchdog timer. * ; * 3. Reads the encoder bits until a change is detected, then * ; * determines the direction the knob was moved. * ; * * ; * Input: Knob input read from port A * ; * ren_old -> the last encoder bits read * ; * last_dir -> the last direction moved * ; * * ; * Output: ren_timer -> an indication the speed of the knob. * ; * ren_new -> the current encoder bits * ; * last_dir -> the last direction (0 = down, 2 = up) * ; * * ; ***************************************************************************** ; poll_encoder clrf ren_timer_1 ; Put starting values in ren_timer cells movlw 0x40 ; Start with the high bit set movwf ren_timer_0 ; in ren_timer_0 read_encoder call PB_look ; Look at PB's for possible 1 MHz jump btfsc ren_timer_1,7 ; Has the bit floated to top of ren_timer_1? goto no_inc ; Yes, don't move it any further movlw 0x08 ; No, keep going addwf ren_timer_0,f ; Add constant to ren_timer_0 btfsc STATUS,B_C ; Did the add force a carry? incf ren_timer_1,f ; Yes, then add one to ren_timer_1 no_inc ; movf PortA,w ; Get the current encoder value movwf ren_read ; Save it movlw 0x03 ; Get encoder mask (to isolate RA0 and RA1) andwf ren_read,w ; Isolated encoder bits into W movwf ren_new ; Save new value xorwf ren_old,w ; Has it changed? btfsc STATUS,B_Z ; Check zero-flag (zero if no change) goto read_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 RA0 and RA1 - the two low order bits 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 2 (UP) because of bit positioning ;============================================================================= bcf STATUS,B_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 andlw 0x02 ; Mask to look at only "Left-Bit" of pair movwf next_dir ; Save result (in W) as direction (bit=UP) xorwf last_dir,w ; See if direction is same as before ; ; Prevent encoder slip from giving a false change in direction. ; btfsc STATUS,B_Z ; Zero flag set? (i.e, is direction same?) goto pe_continue ; Yes, same direction so no slip; keep going movf next_dir,w ; No Zero-flag, so direction changed movwf last_dir ; Update the direction indicator movf ren_new,w ; Save the current encoder bits (now in W) movwf ren_old ; for next time goto read_encoder ; Try again pe_continue clrf last_dir ; Clear last_dir (default is DN) btfss ren_old,1 ; Are we going UP? goto exit3 ; No, exit3 up2 movlw 0x02 ; Get UP value movwf last_dir ; and set in last_dir 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 to see if the pushbuttons PB1 and PB2 ; * being pressed. ; * If both are being pressed, determine direction (depending on ; * which was first) and change the frequency UP/DN by 1 MHz. ; * If only one is being pressed, remember it as being "first". ; * ; * Input: None ; * Output: PB_flag set up ; **************************************************************************** ; PB_look ; btfss PortA,pb_1 ; Is PB_1 being pressed? goto PB_yes1 ; Yes, PB_yes1 PB_no1 ; 0,X btfsc PortA,pb_2 ; Not1, but is PB_2 being pressed? goto PB_neither ; No, Neither goto PB_2butnot1 ; Yes, PB_2butnot1 PB_yes1 ; 1,X btfsc PortA,pb_2 ; Is PB_2 being pressed? goto PB_1butnot2 ; No, PB_1butnot2 ; fall through to PB_both ; Yes, PB_both PB_both ; 1,2 ; both set, so add code to find out which was first. btfsc PB_flags,PB1first ; Was PB1 pressed first? goto PB_up ; Yes, going down by 1 MHz goto PB_dn ; NO, PB2 was first, so going UP by 1 MHz PB_2butnot1 ; 0,2 bsf PB_flags,PB1first ; Set PB1 first goto PB_exit ; Exit PB_1butnot2 ; 1,0 bcf PB_flags,PB1first ; Clear PB1 first goto PB_exit ; Exit PB_neither ; 0,0 clrf PB_flags ; Neither, so clear flags goto PB_exit ; Exit ; PB_up ; ; PB_2 was pressed first, then PB_1 ; Wait for PB_1 to be released ; (If wait is longer than 2 seconds, call update_EEPROM and exit) movlw 0x10 ; Set up movwf PB_wait_count ; loop count PB_up_release_wait ; btfsc PortA,pb_1 ; Is PB_1 still behing held? goto PB_up_released ; No, it's released, so move up call wait_128ms ; Wait a bit decfsz PB_wait_count,f ; Have we waited long enough? goto PB_up_release_wait ; No, continue waiting ; Button was held longer than 2 seconds, so call update_EEPROM ; Update EEPROM with current frequency goto PB_exit ; and exit ; PB_up_released ; clrf fstep_3 ; movlw 0x0F ; Setup for step of 1 M movwf fstep_2 ; movlw 0x42 ; movwf fstep_1 ; movlw 0x40 ; movwf fstep_0 ; call add_step ; Add a step and update call check_add ; See if band limits reached. If so, adjust ; and update goto PB_update ; ; PB_dn ; ; PB_1 was pressed first, then PB_2 ; Wait for PB_2 to be released ; (If wait is longer than 2 seconds, call update_EEPROM and exit) movlw 0x10 ; Set up movwf PB_wait_count ; loop count PB_dn_release_wait ; btfsc PortA,pb_2 ; Is PB_2 still being held? goto PB_dn_released ; No, it's released, so move up call wait_128ms ; Wait a bit decfsz PB_wait_count,f ; Have we waited long enough? goto PB_dn_release_wait ; No, continue waiting ; Button was held longer than 2 seconds, so call update_EEPROM ; Update EEPROM with current frequency goto PB_exit ; and exit ; PB_dn_released ; clrf fstep_3 ; movlw 0x0F ; Setup for step of 1 M movwf fstep_2 ; movlw 0x42 ; movwf fstep_1 ; movlw 0x40 ; movwf fstep_0 ; call sub_step ; Add a step and update ; PB_update ; call bin2BCD ; Convert the frequency to BCD call show_freq ; Display the frequency on the LCD call calc_dds_word ; Find the control word for the DDS chip call send_dds_word ; Send the control word to the DDS chip ; PB_exit ; return ; ; ; ***************************************************************************** ; * * ; * Purpose: This routine is entered at start up if the calibrate * ; * push button is (PIC-EL PB_1) is pressed at power-on time. * ; * * ; * If a 8-character LCD is used,"10000000" is displayed on the LCD * ; * If a 16-character LCD is used," 10,000.000 CAL " is displayed * ; * on the LCD. * ; * * ; * The DDS chip is programmed to produce 10 MHz, based on the * ; * osc value stored in the EEPROM. As long as the button is * ; * pressed, the osc value is slowly altered to allow the output * ; * to be trimmed to exactly 10 MHz. Once the encoder is turned * ; * after the button is released, the new osc value is stored in * ; * the EEPROM and normal operation begins. * ; * * ; * Input: The original osc constant in EEPROM * ; * * ; * Output: The corrected osc constant in EEPROM * ; * * ; ***************************************************************************** ; calibrate movlw 0x80 ; Set frequency to 10MHz by setting freq movwf freq_0 ; to the binary equivalent of 10 MHz movlw 0x96 ; movwf freq_1 ; movlw 0x98 ; movwf freq_2 ; movlw 0x00 ; movwf freq_3 ; ; ; Read the starting reference oscillator value from EEPROM. ; clrf EEadr ; Reset the EEPROM read address call read_EEPROM ; Read EEPROM movf EEdata,w ; Get the first osc byte movwf osc_0 ; Save osc frequency call read_EEPROM ; Get next byte movf EEdata,w ; movwf osc_1 ; Save it call read_EEPROM ; Get the third byte movf EEdata,w ; movwf osc_2 ; Save it call read_EEPROM ; Get the fourth byte movf EEdata,w ; movwf osc_3 ; Save it call bin2BCD ; Calculate BCD version of 10 MHz call show_freq ; Display the frequency on the LCD #IF LCDCHAR == 16 movlw 0xC4 ; Point LCD at position 13 movwf LCD_char ; call cmnd2LCD ; Send command to LCD movlw 'C' ; Send a C (position 13) movwf LCD_char ; call data2LCD ; movlw 'A' ; Send an A (position 14) movwf LCD_char ; call data2LCD ; movlw 'L' ; Send an L (position 15) movwf LCD_char ; call data2LCD ; ; ; (position 16 is blank) #ENDIF cal_loop call calc_dds_word ; Calculate DDS value based on current osc call send_dds_word ; Update the DDS chip call poll_encoder ; Wait until the encoder has moved. clrf fstep_3 ; Clear the three most significant clrf fstep_2 ; bytes of fstep clrf fstep_1 ; movlw 0x10 ; Assume that we are adjusting slowly movwf fstep_0 ; Use small increment btfsc ren_read,2 ; Was the encoder changing slowly? goto update_osc ; Yes, then continue with small increment movlw 0x80 ; No, then use the large increment movwf fstep_0 ; update_osc nop ; Wait a cycle btfsc last_dir,1 ; Are we moving down? goto faster ; No, increase the osc value ; ; slower ; comf fstep_0,f ; Subtraction of fstep is done by comf fstep_1,f ; adding the twos compliment of fsetp comf fstep_2,f ; to osc comf fstep_3,f ; incfsz fstep_0,f ; Increment last byte goto faster ; Non-zero, continue incfsz fstep_1,f ; Increment next byte goto faster ; Non-zero, continue incfsz fstep_2,f ; Increment next byte goto faster ; Non-zero, continue incf fstep_3,f ; Increment the high byte faster movf fstep_0,w ; Get the low byte increment addwf osc_0,f ; Add it to the low osc byte btfss STATUS,B_C ; Was there a carry? goto add4 ; No, add the next bytes incfsz osc_1,f ; Ripple carry up to the next byte goto add4 ; No new carry, add the next bytes incfsz osc_2,f ; Ripple carry up to the next byte goto add4 ; No new carry, add the next bytes incf osc_3,f ; Ripple carry up to the highest byte add4 movf fstep_1,w ; Get the second byte increment addwf osc_1,f ; Add it to the second osc byte btfss STATUS,B_C ; Was there a carry? goto add5 ; No, add the third bytes incfsz osc_2,f ; Ripple carry up to the next byte goto add5 ; No new carry, add the third bytes incf osc_3,f ; Ripple carry up to the highest byte add5 movf fstep_2,w ; Get the third byte increment addwf osc_2,f ; Add it to the third osc byte btfss STATUS,B_C ; Was there a carry? goto add6 ; No, add the fourth bytes incf osc_3,f ; Ripple carry up to the highest byte add6 movf fstep_3,w ; Get the fourth byte increment addwf osc_3,f ; Add it to the fourth byte btfss PortA,pb_1 ; Tuning-increment pushbutton pressed? 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: This routine will save the current frequency in EEPROM. This ; * frequency will then be used as the initial frequency upon start ; * up. The routine is entered by pressing and holding both PB_1 ; * and PB_2 for more than 2 seconds. . ; * ; * Input: The original osc constant in EEPROM ; * ; * Output: The corrected osc constant in EEPROM ; * ; **************************************************************************** ; update_EEPROM ; movlw EEstartup_adr ; Get startup address movwf EEadr ; and set up for start of EEPROM writes movf freq_0,w ; Record the first movwf EEdata ; freq call write_EEPROM ; byte movf freq_1,w ; Record the second movwf EEdata ; osc call write_EEPROM ; byte movf freq_2,w ; Record the third movwf EEdata ; osc call write_EEPROM ; byte movf freq_3,w ; Record the fourth movwf EEdata ; osc call write_EEPROM ; byte return ; ; ; ***************************************************************************** ; * * ; * 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,B_C ; Start with Carry clear btfss osc_temp_0,0 ; Is bit 0 (Least Significant bit) set? goto noAdd ; No, don't need to add freq term to total movf freq_0,w ; Yes, get the freq_0 term addwf AD9851_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 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 ; Use the freq_1 term addwf AD9851_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 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 ; Use the freq_2 term addwf AD9851_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 AD9851_4,f ; Yes, add one and continue add9 movf freq_3,w ; Use the 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) D #endif #ifdef AD9851 movlw 0x01 ; Turn on 6x clock multiplier (AD9851) D #endif movwf AD9851_4 ; Last byte to be sent D ; Mult answer is in bytes _3 .. _0 D 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,B_C ; Was it zero? goto send0 ; Yes, send zero bsf PortB,DDS_dat ; No, send one bsf PortB,DDS_clk ; Toggle write clock bcf PortB,DDS_clk ; goto break ; send0 bcf PortB,DDS_dat ; Send zero bsf PortB,DDS_clk ; Toggle write clock bcf PortB,DDS_clk ; break decfsz bit_count,f ; Has the whole byte been sent? goto next_bit ; No, keep going. incf FSR,f ; Start the next byte unless finished movlw AD9851_4+1 ; Next byte (past the end) subwf FSR,w ; btfss STATUS,B_C ; goto next_byte ; bsf PortB,DDS_load ; Send load signal to the AD9850/AD9851 bcf PortB,DDS_load ; return ; ; ; ***************************************************************************** ; * * ; * Purpose: This subroutine converts a 32 bit binary number to a 10 digit * ; * BCD number. The input value taken from freq(0 to 3) is * ; * preserved. The output is in BCD(0 to 4), each byte holds => * ; * (hi_digit,lo_digit), most significant digits are in BCD_4. * ; * This routine is a modified version of one described in * ; * MicroChip application note AN526. * ; * * ; * Input: The value in freq_0 ... freq_3 * ; * * ; * Output: The BCD number in BCD_0 ... BCD_4 * ; * * ; ***************************************************************************** ; bin2BCD movlw 0x20 ; Set loop counter movwf BCD_count ; to 32 clrf BCD_0 ; Clear output clrf BCD_1 ; " " clrf BCD_2 ; " " clrf BCD_3 ; " " clrf BCD_4 ; " " bin_loop bcf STATUS,B_C ; Clear carry bit in STATUS ; ; Rotate bits in freq bytes. Move from LS byte (freq_0) to next byte (freq_1). ; Likewise, move from freq_1 to freq_2 and from freq_2 to freq_3. ; rlf freq_0,f ; Rotate left, 0 -> LS bit, MS bit -> Carry rlf freq_1,f ; Rotate left, Carry->LS bit, MS bit->Carry rlf freq_2,f ; Rotate left, Carry->LS bit, MS bit->Carry rlf freq_3,f ; Rotate left, Carry->LS bit, MS bit->Carry btfsc STATUS,B_C ; Is Carry clear? If so, skip next instruction bsf freq_0,0 ; Carry is set so wrap and set bit 0 in freq_0 ; ; Build BCD bytes. Move into LS bit of BCD bytes (LS of BCD_0) from MS bit of ; freq_3 via the Carry bit. ; rlf BCD_0,f ; Rotate left, Carry->LS bit, MS bit->Carry rlf BCD_1,f ; Rotate left, Carry->LS bit, MS bit->Carry rlf BCD_2,f ; Rotate left, Carry->LS bit, MS bit->Carry rlf BCD_3,f ; Rotate left, Carry->LS bit, MS bit->Carry rlf BCD_4,f ; Rotate left, Carry->LS bit, MS bit->Carry decf BCD_count,f ; Decrement loop count btfss STATUS,B_Z ; Is loop count now zero? goto adjust ; No, go to adjust return ; Yes, EXIT ; ============================================================================ adjust ; Internal subroutine, called by bin2BCD main loop only ; ; As BCD bytes are being built, make sure the nibbles do not grow larger than 9. ; If a nibble gets larger than 9, increment to next higher nibble. ; (If the LS nibble of a byte overflows, increment the MS nibble of that byte.) ; (If the MS nibble of a byte overflows, increment the LS nibble of next byte.) ; movlw BCD_0 ; Get pointer to BCD_0 movwf FSR ; Put pointer in FSR for indirect addressing call adj_BCD ; incf FSR,f ; Move indirect addressing pointer to BCD_1 call adj_BCD ; incf FSR,f ; Move indirect addressing pointer to BCD_2 call adj_BCD ; incf FSR,f ; Move indirect addressing pointer to BCD_3 call adj_BCD ; incf FSR,f ; Move indirect addressing pointer to BCD_4 call adj_BCD ; goto bin_loop ; Back to main loop of bin2BCD ; ============================================================================ adj_BCD ; Internal subroutine, called by adjust only movlw 3 ; Add 3 addwf INDF,w ; to LS digit movwf BCD_temp ; Save in temp btfsc BCD_temp,3 ; Is LS digit + 3 > 7 (Bit 3 set) movwf INDF ; Yes, save incremented value as LS digit movlw 0x30 ; Add 3 addwf INDF,w ; to MS digit movwf BCD_temp ; Save as temp btfsc BCD_temp,7 ; Is MS digit + 3 > 7 (Bit 7 set) movwf INDF ; Yes, save incremented value as MS digit return ; Return to adjust subroutine ; ; ***************************************************************************** ; * * ; * Purpose: Display the frequency setting on the LCD. * ; * If a 1x8 LCD display so display freq in Hz - e.g. 14025000 * ; * If a 1x16 LCD display so display freq kHz - e.g 14,025.000 kHz * ; * * ; * Input: The values in BCD_4 ... BCD_0 * ; * * ; * Output: The number displayed on the LCD * ; * * ; ***************************************************************************** ; show_freq movlw 0x80 ; Point the LCD to first LCD digit location call cmnd2LCD ; Send starting digit location to LCD #IF LCDCHAR == 16 movlw ' ' ; Send a space call data2LCD ; to position 1 of LCD #ENDIF ; ; Running 4-bit mode, so need to send Most Significant Nibble first. ; ; Extract and send "XXXX" from byte containing "XXXXYYYY" ; - Swap halves to get YYYYXXXX ; - Mask with 0x0F to get 0000XXXX ; - Add ASCII bias (0030XXXX) ; swapf BCD_3,w ; Swap 10MHz BCD digit into lower nibble of W andlw 0x0F ; Mask for lower nibble only (0000XXXX) addlw 0x30 ; Add offset for ASCII char set (0030XXXX) call data2LCD ; Send byte in W to LCD ; ; Extract and send "YYYY" from byte containing "XXXXYYYY" ; - Mask with 0x0F to get 0000YYYY ; - Add offset for ASCII character set in LCD (0030YYYY) ; movf BCD_3,w ; Put 1MHz BCD digit into lower nibble of W andlw 0x0F ; Mask for lower nibble only (0000YYYY) addlw 0x30 ; Add offset for ASCII char set (0030YYYY) call data2LCD ; Send byte in W to LCD ; #IF LCDCHAR == 16 movlw ',' ; Get a comma call data2LCD ; Send byte in W to LCD #ENDIF ; swapf BCD_2,w ; Swap 100KHz BCD digit into lower nibble of W andlw 0x0F ; Mask for lower nibble only (0000XXXX) addlw 0x30 ; Add offset for ASCII char set (0030XXXX) call data2LCD ; Send byte in W to LCD ; movf BCD_2,w ; Put 10KHz BCD digit into lower nibble of W andlw 0x0F ; Mask for lower nibble only (0000YYYY) addlw 0x30 ; Add offset for ASCII char set (0030YYYY) call data2LCD ; Send byte in W to LCD ; swapf BCD_1,w ; Swap 1KHz BCD digit into lower nibble of W andlw 0x0F ; Mask for lower nibble only (0000XXXX) addlw 0x30 ; Add offset for ASCII char set (0030XXXX) call data2LCD ; Send byte in W to LCD ; #IF LCDCHAR == 16 movlw '.' ; Get a period call data2LCD ; Send byte in W to LCD movlw 0xC0 ; Point to LCD digit number nine call cmnd2LCD ; Send command byte in W to LCD #ENDIF ; movf BCD_1,w ; Put 100 Hz BCD digit into lower nibble of W andlw 0x0F ; Mask for lower nibble only (0000YYYY) addlw 0x30 ; Add offset for ASCII char set (0030YYYY) call data2LCD ; Send data byte in W to LCD ; swapf BCD_0,w ; Swap 10 Hz BCD digit into lower nibble of W andlw 0x0F ; Mask for lower nibble only (0000XXXX) addlw 0x30 ; Add offset for ASCII char set (0030XXXX) call data2LCD ; Send data byte in W to LCD ; movf BCD_0,w ; Put 1 Hz BCD digit into lower nibble of W andlw 0x0F ; Mask for lower nibble only (0000YYYY) addlw 0x30 ; Add offset for ASCII char set (0030YYYY) call data2LCD ; Send byte in W to LCD ; #IF LCDCHAR == 16 movlw ' ' ; Send a space call data2LCD ; to position 12 of LCD ; movlw 'k' ; Send a 'k' call data2LCD ; to position 13 of LCD ; movlw 'H' ; Send an "H" call data2LCD ; to position 14 of LCD ; movlw 'z' ; Send a 'z' call data2LCD ; to position 15 of LCD ; movlw ' ' ; Send a space call data2LCD ; to position 16 of LCD #ENDIF ; return ; ; ; ***************************************************************************** ; * * ; * Purpose: Check if LCD is done with the last operation. * ; * This subroutine polls the LCD busy flag to determine if * ; * previous operations are completed. * ; * * ; * Input: None * ; * * ; * On exit: PortB set as: RB3 input * ; * all others outputs * ; ***************************************************************************** ; busy_check clrf PortB ; Clear all outputs on PortB bsf STATUS,B_RP0 ; Switch to bank 1 for Tristate operation movlw b'00001000' ; Set RB3 input, others outputs movwf TRISB ; via Tristate bcf STATUS,B_RP0 ; Switch back to bank 0 bcf PortB,LCD_rs ; Set up LCD for Read Busy Flag (RS = 0) bsf PortB,LCD_rw ; Set up LCD for Read (RW = 1) movlw 0xFF ; Set up constant 255 movwf timer1 ; for timer loop counter LCD_is_busy bsf PortB,LCD_e ; Set E high movf PortB,w ; Read PortB into W movwf LCD_read ; Save W for later testing bcf PortB,LCD_e ; Drop E again nop ; Wait a nop ; while bsf PortB,LCD_e ; Pulse E high (dummy read of lower nibble), nop ; wait, bcf PortB,LCD_e ; and drop E again decf timer1,f ; Decrement loop counter btfsc STATUS,B_Z ; Is loop counter down to zero? goto not_busy ; If yes, return regardless ;OLD btfsc LCD_read,7 ; Is Busy Flag (RB7) in save byte clear? btfsc LCD_read,LCD_busy ; Busy Flag (RB3) in save byte clear? goto LCD_is_busy ; If not, it is busy so jump back not_busy return ; ; ; ***************************************************************************** ; * Purpose: Send Command or Data byte to the LCD * ; * Entry point cmnd2LCD: Send a Command to the LCD * ; * Entry Point data2LCD: Send a Data byte to the LCD * ; * * ; * Input: W has the command or data byte to be sent to the LCD. * ; * * ; * Output: None * ; ***************************************************************************** ; cmnd2LCD ; ****** Entry point ****** movwf LCD_char ; Save byte to write to LCD clrf rs_value ; Remember to clear RS (clear rs_value) bcf PortB,LCD_rs ; Set RS for Command to LCD goto write2LCD ; Go to common code data2LCD ; ****** Entry point ******** movwf LCD_char ; Save byte to write to LCD bsf rs_value,0 ; Remember to set RS (set bit 0 of rs_value) bsf PortB,LCD_rs ; Set RS for Data to LCD write2LCD call busy_check ; Check to see if LCD is ready for new data clrf PortB ; Clear all of Port B (inputs and outputs) bsf STATUS,B_RP0 ; Switch to bank 1 for Tristate operation movlw 0x00 ; Set up to enable PortB data pins movwf TRISB ; All pins (RB7..RB0) are back to outputs bcf STATUS,B_RP0 ; Switch to bank 0 bcf PortB,LCD_rw ; Set LCD back to Write mode (RW = 0) bcf PortB,LCD_rs ; Guess RS should be clear btfsc rs_value,0 ; Should RS be clear? (is bit 0 == 0?) bsf PortB,LCD_rs ; No, set RS ; ; Transfer Most Significant nibble (XXXX portion of XXXXYYYY) ; movlw 0xF0 ; Set up mask andwf PortB,f ; Keep RB7..RB4 but clear old RB3..RB0 swapf LCD_char,w ; Put byte into W (reverse nibbles) andlw 0x0F ; Mask to give 0000XXXX in W iorwf PortB,f ; To RB3..RB0 with RB7..RB4 unchanged bsf PortB,LCD_e ; Pulse the E line high, nop ; wait, bcf PortB,LCD_e ; and drop it again ; ; Transfer Least Significant nibble (YYYY portion of XXXXYYYY) ; movlw 0xF0 ; Set up mask andwf PortB,f ; Clear old RB3..RB0 movf LCD_char,w ; Move LS nibble of into W andlw 0x0F ; Mask to give 0000YYYY in W iorwf PortB,f ; To RB3..RB0 with RB7..RB4 unchanged bsf PortB,LCD_e ; Pulse the E line high, nop ; wait, bcf PortB,LCD_e ; and drop it again return ; ; ***************************************************************************** ; * * ; * Purpose: Write the byte of data at EEdata to the EEPROM at address * ; * EEadr. * ; * * ; * Input: The values at EEdata and EEadr. * ; * * ; * Output: The EEPROM value is updated. * ; * * ; ***************************************************************************** ; write_EEPROM bsf STATUS,B_RP0 ; Switch to bank 1 bsf EEdata,WREN ; Set the EEPROM write enable bit movlw 0x55 ; Write 0x55 and 0xAA to EEPROM movwf EEadr ; control register, as required movlw 0xAA ; for the write movwf EEadr ; bsf EEdata,WR ; Set WR to initiate write bit_check btfsc EEdata,WR ; Has the write completed? goto bit_check ; No, keep checking bcf EEdata,WREN ; Clear the EEPROM write enable bit bcf STATUS,B_RP0 ; Switch to bank 0 incf EEadr,f ; Increment the EE write address return ; Return to the caller ; ; ***************************************************************************** ; * * ; * Purpose: Read a byte of EEPROM data at address EEadr into EEdata. * ; * * ; * Input: The address EEadr. * ; * * ; * Output: The value in EEdata. * ; * * ; ***************************************************************************** ; read_EEPROM bsf STATUS,B_RP0 ; Switch to bank 1 bsf EEdata,RD ; Request the read bcf STATUS,B_RP0 ; Switch to bank 0 incf EEadr,f ; Increment the read address return ; Return to the caller ; ; ***************************************************************************** ; * * ; * Purpose: Wait for a specified number of milliseconds. * ; * * ; * Entry point wait_a_sec: Wait for 1 second * ; * Entry point wait_256ms: Wait for 256 msec * ; * Entry point wait_128ms: Wait for 128 msec * ; * Entry point wait_64ms : Wait for 64 msec * ; * Entry point wait_32ms : Wait for 32 msec * ; * Entry point wait_16ms : Wait for 16 msec * ; * Entry point wait_8ms : Wait for 8 msec * ; * * ; * Input: None * ; * * ; * Output: None * ; * * ; ***************************************************************************** ; wait_a_sec ; ****** Entry point ****** call wait_256ms ; call wait_256ms ; call wait_256ms ; call wait_256ms ; return wait_256ms ; ****** Entry point ****** call wait_128ms ; call wait_128ms ; return wait_128ms ; ****** Entry point ****** movlw 0xFF ; Set up outer loop movwf timer1 ; counter to 255 goto outer_loop ; Go to wait loops wait_64ms ; ****** Entry point ****** movlw 0x80 ; Set up outer loop movwf timer1 ; counter to 128 goto outer_loop ; Go to wait loops wait_32ms ; ****** Entry point ****** movlw 0x40 ; Set up outer loop movwf timer1 ; counter to 64 goto outer_loop ; Go to wait loops wait_16ms ; ****** Entry point ****** movlw 0x20 ; Set up outer loop movwf timer1 ; counter to 32 goto outer_loop ; Go to wait loops wait_8ms ; ****** Entry point ****** movlw 0x10 ; Set up outer loop movwf timer1 ; counter to 16 ; Fall through into wait loops ; ; Wait loops used by other wait routines ; - 1 microsecond per instruction (with a 4 MHz microprocessor crystal) ; - 510 instructions per inner loop ; - (Timer1 * 514) instructions (.514 msec) per outer loop ; - Round off to .5 ms per outer loop ; outer_loop movlw 0xFF ; Set up inner loop counter movwf timer2 ; to 255 inner_loop decfsz timer2,f ; Decrement inner loop counter goto inner_loop ; If inner loop counter not down to zero, ; then go back to inner loop again decfsz timer1,f ; Yes, Decrement outer loop counter goto outer_loop ; If outer loop counter not down to zero, ; then go back to outer loop again return ; Yes, return to caller ; ; ***************************************************************************** ; END