; Signal Generator (LO for Transceiver) w/AE4GX TX offset and RIT Mods 6/9/99 ; file name sgLO.txt ; ; New Features: ; TRANSMIT OFFSET shifts from the current Receive frequency (RX direct or RIT) ; to the last Transmit frequency determined while in direct (no RIT) receive mode. ; The Transmit Offset is software set: currently default to -750 Hz (minus 750Hz) from ; direct Receive frequency. On power up you can override the default settings to ; +/- any number Hertz by use of the encoder, display and Band Change and TX active ; push button switches. ; ; The FAR circuit board was MODed to allow RA4 line to be pulled up via 10k resistor. The line wine be ; pulled low during transmit periods. This line is used inconjunction with RA3 for other functions ; at specific times. ; ; Receive Incremental Tuning (RIT) can be toggled on or off via push button ; switch. While in RIT mode the Transmit frequency cannot be altered. Currently ; there is no limit in the RIT range +/- from Tx frequency. On exit from RIT mode the ; Rx frequency reverts back to pre-RIT value and TX w/offset sames same. If the ; RX mode is non-RIT then the receive frequency will be displayed iwth RX following the number. ; If in RIT mode the receive frequceny will be displayed with RIT following the number. ; ; RIT can be toggled on/off at any time the code is is Receive mode. This signal uses low states ; of RA4 and RA3 simultaneously. I accomplish this by steering diodes to an RIT switch. ; ; Front Panel Switches: ; ; Encoder: No change ; ; Band Change: diode added to RA3 path. ; ; TX Spot: (new) diode added to RA4 path. A path from the xcvr keying module ; will be interfaced to this RA4 independently via a separate diode. ; ; RIT Toggle: (new) diodes to both RA3 and RA4 paths. ; ; Display MODs: ; ; In Receive mode with no RIT active you see mm,kkk.cccRx ; ; In Receive mode with RIT active you see mm,kkk.cccRIT ; ; In Transmit mode RA4 low and RA3 high mm,kkk.cccTx ; ; If Transmit mode RA4 low is held during power up 00,000.750TXO ; As long as TX is active you can change the value with the encoder ; When you release TX RA4 low you'll see xx,xxx.xxxNEG- Press TX again to accept ; If you want positive offset then press Band Change and you'll see xx,xxx.xxxPOS+ ; To accept positive offset press TX now. This is a one way one time choice to keep ; the code simple since this should be rarely used. ; ; Note: In this implementation I have turned off the Watchdog timer. ; It can be implemented but the check-encoder routine from 3a and ; versions use this to avoid lockout. Since I'm using this in ; transceiver control mode I would rather have the environment ; just lock up rather automatically drop back to some initial frequency ; and band state. More to be done here. ; ; ; I have extensively used the code available in version 3a. Now that I've hacked my way around ; I would like to rewrite it but time is short so be my guest. Thanks to Curtis, Bruce and Craig ; for all the previous foundation that is available. ; ; My goal was simply to build a DDS VFO with trnsceiver features that could be used ; in a homebrew rig of my dreams. The initial version will be mounted on a test-bed platform ; to be used in testing various transceiver modules to be build. At some point the ; results will be ported into a useable QRP transceiver that perform on all HF bands. ; ; As time permits I will be posting my experiments using this LO module on my web page. Please ; check it out once and awhile. http://www. ; **************************************************************************** ; * Signal Generator (VFO) with Direct Digital Synthesis * ; * Version 3a * ; * April 21, 1999 * ; * * ; **************************************************************************** ; Description: ; This is the control program for a DDS VFO built with an AD9850 DDS chip, a ; shaft encoder, a push button switch and an Liquid crystal display. ; ; Features: ; VARIABLE RATE TUNING based on the speed at which the encoder is turned. The ; encoder also has a built in switch which will change the step size from 1Hz ; to 1kHz if the encoder shaft is pressed down while turning. ; ; BAND MEMORIES an external push button switch allows the frequency to be ; cycled around the HF ham bands. ; ; CALIBRATE MODE is entered if the external push button is pressed during ; power on. The display is set to 10,000.000 CAL and remains fixed, even as ; adjustments are being made. If the push button is held pressed, then turning ; the shaft encoder will increase or decrease the value "osc" used to ; calculate the DDS control word. The basic calibrate adjustment rate is very ; low (on the order of a few cycles per turn of the encoder). A somewhat ; faster adjustment speed is available by pressing the encoder shaft down ; while turning. An external frequency counter on the DDS output is required ; to observe this adjustment. To exit calibrate mode, release the external ; push button and turn the shaft encoder one more time. The calibrated value ; of "osc" will then be stored in EEPROM memory. ; ;****************************************************************************** ; Author - Curtis W. Preuss - WB2V ; ; Modification History ; 8/19/98 - Version 1 - Initial Version by Curtis W. Preuss - WB2V ; 12/xx/98 - Version 2 - Converted to MPASM by Bruce Stough, AA0ED ; 4/21/99 - Version 3 - Fixed and modified by Bruce Stough, AA0ED and ; Craig Johnson, AA0ZZ ; FIXES: ; 1) Fix a bug which caused the frequency to jump to ; the maximum when going towards zero. ; 2) Fix several SMASM to MPASM translation bugs. ; - Code worked, but several cases of "hard coded" ; constants remained that should have been ; changed to labels to allow data tables to ; be moved and/or modified. This bug could ; cause a reference to a wrong variable. ; - PortB vs TRISB causing confusion. ; MODIFICATIONS/ADDITIONS: ; 1) The lower frequency is changed from 1Khz to zero. ; 2) Added band table entries of 0 Hz and 30 MHz. ; 3) Changed wait routine names for clarity. ; 4) Added comments throughout. ; 5) Subroutine headers added. ; - Inputs and Outputs specified ; 6) Changed some data labels for clarity. ; 7) Changed some routines for efficiency. ; - Improve path length and save memory ; NOTE: OUR GOAL was to make this code clear and ; easy to understand so that it can be used as ; a springboard for additional changes. ; By documenting subroutines as clearly as ; possible, we hope readers will be able to ; easily use the subroutines in other projects. ; ;***************************************************************************** ; ; Target Controller - PIC16F84 ; __________ ; ENCODER SWITCH--RA2 |1 18| RA1---------ENCODER A ; PB SWITCH-------RA3 |2 17| RA0---------ENCODER B ; +5V-------------RA4 |3 16| OSC1--------XTAL ; Ground--------!MCLR |4 15| OSC2--------XTAL ; Ground----------Vss |5 14| VDD---------+5 V ; DDS LOAD--------RB0 |6 13| RB7---------DDS DATA/LCD 14 ; LCD_rs----------RB1 |7 12| RB6---------LCD 13 ; LCD_rw----------RB2 |8 11| RB5---------DDS CLOCK/LCD 12 ; LCD_e-----------RB3 |9 10| RB4---------LCD 11 ; ---------- ; ;***************************************************************************** ; ; ********** HARDWARE CHANGES NEEDED TO FAR CIRCUITS BOARD ********** ; ; The circuit board available from FAR Circuits is based on the original ; QEX article (7/97) by Curt, WB2V, and works with the original dds_vfo ; code. Subsequently, Curt modified the code to add the calibrate ; and band select functions. ; ; This enhanced PIC code requires a hardware change to allow for the push ; button which is used to select the calibrate mode and to do band ; selection. The change removes the +5 volt connection to pin 2 of ; the PIC chip, and connects a 10K ohm pull-up resistor to pin 2. The ; push button is connected between pin 2 and ground, so that the pin is ; grounded when the button is pressed and held high when the button is ; released. In addition, instead of using the output of Pin 2 to power ; the LCD and control the LCD contrast, the LCD is connected directly to ; +5 volts. ; ; The change can be done as follows: ; ; 1. Locate the PC trace which connects pin 2 of the PIC chip to the ; LCD contrast control pot. This was originally used to power the ; LCD by setting bit 3 of Port A (pin 2) high. Drill a small hole ; (#60 - .040") through this trace as near as possible (1/16") to ; pin 2 of the PIC socket. ; ; 2. Near pin 2 of the PIC socket, cut the trace about 1/8" beyond the ; new hole. Using a 1/4" drill, remove the foil around the hole ; on the ground plane side of the board to avoid shorting pin 2 to ; ground when the resistor gets inserted in the hole (Step 4). ; ; 3. Locate the +5 volt trace that passes between pin 1 and pin 2 of the ; PIC socket. Drill a new hole through this trace in the place where ; it is close to pin 1. Remove the ground side foil from around this ; hole with a 1/4" drill to avoid shorting +5 v to ground when the ; resistor lead is inserted. ; ; 4. Install a 10K resistor in the new PC board holes. Mount it vertically, ; with the short resistor lead in the +5 v trace hole and the other end ; bent close to the resistor body and down into the hole near pin 2 of ; the PIC socket. Make the solder connection to the traces only, not to ; ground plane foil on the other side of the board. Make sure both sides ; of the +5 v trace are soldered to the resistor lead. ; ; 5. Connect a wire from one side of the push button to the pin 2 side of ; the new 10K resistor. This can be done by connecting the wire to the ; top of the vertically mounted resistor. Connect another wire from the ; other side of the push button to ground. ; ; 6. Using a bit of solid wire, connect the pin of the LCD contrast ; pot that used to be connected to pin 2 of the PIC socket to the ; +5 volt trace. This now supplies power to the LCD. ; ; **************************************************************************** ; * Device type and options. * ; **************************************************************************** ; processor PIC16F84 radix dec ; ; **************************************************************************** ; * Configuration fuse information: * ; **************************************************************************** _CP_ON EQU H'000F' _CP_OFF EQU H'3FFF' _PWRTE_ON EQU H'3FF7' _PWRTE_OFF EQU H'3FFF' _WDT_ON EQU H'3FFF' _WDT_OFF EQU H'3FFB' _LP_OSC EQU H'3FFC' _XT_OSC EQU H'3FFD' _HS_OSC EQU H'3FFE' _RC_OSC EQU H'3FFF' ; __config _CP_OFF & _PWRTE_ON & _WDT_ON & _XT_OSC ; ; **************************************************************************** ; * General equates. These may be changed to accommodate the reference clock* ; * frequency, the desired upper frequency limit, and the default startup * ; * frequency. * ; **************************************************************************** ; ; ref_osc represents the change in the frequency control word which results ; in a 1 Hz change in output frequency. It is interpreted as a fixed point ; integer in the format . ; ; The values for common oscillator frequencies are as follows: ; ; Frequency ref_osc_3 ref_osc_2 ref_osc_1 ref_osc_0 ; ; 120.00 MHz 0x23 0xCA 0x98 0xCE ; 100.00 MHz 0x2A 0xF3 0x1D 0xC4 ; 90.70 MHz 0x2F 0x5A 0x82 0x7A ; 66.66 MHz 0x40 0x6E 0x52 0xE7 ; 66.00 MHz 0x41 0x13 0x44 0x5F ; 50.00 MHz 0x55 0xE6 0x3B 0x88 ; ; To calculate other values: ; ref_osc_3 = (2^32 / oscillator_freq_in_Hertz). ; ref_osc_2, ref_osc_1, and ref_osc_0 are the fractional part of ; (2^32 / oscillator_freq_in_Hertz) times 2^24. ; Note: 2^32 = 4294967296 and 2^24 = 16777216 ; ; For example, for a 120 MHz clock: ; ref_osc_3 is (2^32 / 120 x 10^6) = 35.791394133 truncated to 35 (0x23) ; ref_osc_2 is the high byte of (.791394133 x 2^24) = 13277390.32 ; 13277390.32 = 0xCA98CE, so high byte is CA. ; ref_osc_1 is the next byte of 0xCA98CE, or 98 ; ref_osc_0 is the last byte of 0xCA98CE, or CE ; ;==== comment out for 66.000 MHz Oscillator ======= ; ref_osc_3 equ 0x41 ; Most significant osc byte ; ref_osc_2 equ 0x13 ; Next byte ; ref_osc_1 equ 0x44 ; Next byte ; ref_osc_0 equ 0x5f ; Least significant byte ; ; ;==== set for 66.666 MHz Oscillator ======= ref_osc_3 equ 0x40 ; Most significant osc byte ref_osc_2 equ 0x6E ; Next byte ref_osc_1 equ 0x52 ; Next byte ref_osc_0 equ 0xE7 ; Least significant byte ; ; Limit contains the upper limit frequency as a 32 bit integer. ; This should not be set to more than one third of the reference oscillator ; frequency. The output filter of the DDS board must be designed to pass ; frequencies up to the maximum. ; limit_3 equ 0x01 ; Most significant byte for 30 MHz limit_2 equ 0xC9 ; Next byte limit_1 equ 0xC3 ; Next byte limit_0 equ 0x80 ; Least significant byte ; ; Default contains the default startup frequency as a 32 bit integer. ; default_3 equ 0x00 ; Most significant byte for 14.025 MHz default_2 equ 0xD6 ; Next byte default_1 equ 0x01 ; Next byte default_0 equ 0x28 ; Least significant byte ; band_end equ 0x28 ; The offset to the last band table entry ; ; **************************************************************************** ; * Port and EEPROM Constants * ; **************************************************************************** ; PortA equ 0x05 PortB equ 0x06 TRISA equ 0x05 TRISB equ 0x06 EEdata equ 0x08 EEadr equ 0x09 WREN equ 0x02 WR equ 0x01 RD equ 0x00 ; ; **************************************************************************** ; * ID location information: * ; * (MPASM warns about DW here, don't worry) * ; **************************************************************************** ; ORG 0x2000 DATA 0x007F DATA 0x007F DATA 0x007F DATA 0x007F ; ; ; **************************************************************************** ; * Setup the initial constant, based on the frequency of the reference * ; * oscillator. This can be tweaked with the calibrate function. * ; **************************************************************************** ; ORG 0x2100 DATA ref_osc_0 DATA ref_osc_1 DATA ref_osc_2 DATA ref_osc_3 ; ; Clear unused EEPROM bytes. ; DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; ; **************************************************************************** ; * RAM page independent file registers: * ; **************************************************************************** ; INDF EQU 0x00 PCL EQU 0x02 STATUS EQU 0x03 FSR EQU 0x04 PCLATH EQU 0x0A INTCON EQU 0x0B ; ; ***************************************************************************** ; * Bit numbers for the STATUS file register: * ; ***************************************************************************** ; B_RP0 EQU 5 B_NTO EQU 4 B_NPD EQU 3 B_Z EQU 2 B_DC EQU 1 B_C EQU 0 ; ; **************************************************************************** ; * Assign names to IO pins. * ; **************************************************************************** ; ; B register bits: ; DDS_load equ 0x00 ; Update pin on AD9850 LCD_rs equ 0x01 ; 0=instruction, 1=data LCD_rw equ 0x02 ; 0=write, 1=read LCD_e equ 0x03 ; 0=disable, 1=enable DDS_clk equ 0x05 ; AD9850 write clock DDS_dat equ 0x07 ; AD9850 serial data input ; ; A register bits: ; pb_switch equ 0x03 ; Calibrate Push Button, (active low) ; ; ; ; **************************************************************************** ; * Assign names to PORTA byte. * ; **************************************************************************** ; RA4 equ 4 RA3 equ 3 RA2 equ 2 ; ; **************************************************************************** ; * trflag (TX/RX flag) equates ; **************************************************************************** txo equ 0 rito equ 1 init_flag equ 2 sgn_txo equ 3 ;0=minus 1=plus tx ofset from RX freq ; *************************************************************************** ; * Transmit Offset default equates Using 750 Hz = 0x2EE as Offset ; *************************************************************************** ; voff_0 equ 0xEE voff_1 equ 0x02 voff_2 equ 0x00 voff_3 equ 0x00 ; *************************************************************************** ; * ; *************************************************************************** ; **************************************************************************** ; * Allocate variables in general purpose register space * ; **************************************************************************** ; CBLOCK 0x0c ; Start Data Block ; freq_0 ; Display frequency (hex) freq_1 ; (4 bytes) freq_2 freq_3 BCD_0 ; Display frequency (BCD) BCD_1 ; (5 bytes) BCD_2 BCD_3 BCD_4 AD9850_0 ; AD9850 control word AD9850_1 ; (5 bytes) AD9850_2 AD9850_3 AD9850_4 fstep_0 ; Frequency inc/dec fstep_1 ; (4 bytes) fstep_2 fstep_3 BCD_count ; Used in bin2BCD routine BCD_temp ; " mult_count ; Used in calc_dds_word bit_count ; " byte2send ; osc_0 ; Current oscillator osc_1 ; (4 bytes) osc_2 osc_3 osc_temp_0 ; Oscillator frequency osc_temp_1 ; (4 bytes) osc_temp_2 osc_temp_3 LCD_char ; Character being sent to the LCD LCD_read ; Character read from the LCD timer1 ; Used in delay routines timer2 ; " ren_timer_0 ; For variable rate tuning ren_timer_1 ; (2 bytes) ren_new ; New value of encoder pins A and B ren_old ; Old value of encoder pins A and B ren_read ; Encoder pins A and B and switch pin last_dir ; Indicates last direction of encoder next_dir ; Indicates expected direction count ; loop counter (gets reused) band ; Used to index a table of frequencies rs_value ; The LCD rs line flag value ; ; ************************************************************************* ; * Variables for Tx Offset and RIT Logic added by AE4GX 5/20/99 ; ************************************************************************* trflags ;control bits for Tx/RX and RIT switching rxf_0 ; Receive frequency if NO RIT rxf_1 rxf_2 rxf_3 ritf_0 ; Receive frequency if RIT active ritf_1 ritf_2 ritf_3 txf_0 ; Transmit Frequency (includes - offset) txf_1 txf_2 txf_3 toff_0 ; Transmit offest in Hertz (binary value) toff_1 toff_2 toff_3 dsp_0 dsp_1 dsp_2 dsp_3 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 bcf trflags,init_flag ; clr init flag (need for txo_chk) bcf trflags,sgn_txo ; set tx offset to default negative (-) clrf INTCON ; No interrupts for now bsf STATUS,B_RP0 ; Switch to bank 1 bsf 0x01,7 ; Disable weak pullups movlw 0xFF ; Tristate port A movwf TRISA ; clrf TRISB ; Set port B to all outputs bcf STATUS,B_RP0 ; Switch back to bank 0 call init_LCD ; Initialize the LCD ; ; Enter Calibrate Mode if push button is pressed while turning the ; power on. ; btfsc PortA,pb_switch ; Is the switch pressed? goto read_EEocs ; No, get clock freq from EEPROM call calibrate ; Yes, calibrate ; ; Get the reference oscillator constant from the EEPROM. ; read_EEocs clrf EEadr ; Reset the EEPROM read address call read_EEPROM ; Read EEPROM movf EEdata,w ; Get the first osc byte movwf osc_0 ; Save osc frequency call read_EEPROM ; Get next byte movf EEdata,w ; movwf osc_1 ; Save it call read_EEPROM ; Get the third byte movf EEdata,w ; movwf osc_2 ; Save it call read_EEPROM ; Get the fourth byte movf EEdata,w ; movwf osc_3 ; Save it ; ; Set default Tx offset value in to freq ; call voff_to_freq ; setup default TXO to freq call freq_to_toff ; setup initial toff value call bin2BCD call show_freq ; show offset call toff_to_freq ; reestablish freq with current toff value ; ; Get the power on encoder value. ; movf PortA,w ; Read port A movwf ren_read ; Save it in ren_read movlw 0x03 ; Get encoder mask andwf ren_read,w ; Get encoder bits movwf ren_old ; Save in ren_old ; ; Initialize encoder 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 ; ; * check to see if TX Offset needs to be changed ; btfsc PortA,RA4 ;TXO change desired? goto txo_no_chg ; no chk_txoffset_chg call poll_encoder ;check for change btfsc PortA,RA4 ;is TX set low (active)? goto value_chg_done 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,2 ; Is the encoder switch pressed? goto go_step_txo ; 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_txo ; 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_txo 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_txo 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_txo ; No, then double the step size ; ; * step determined ; ; ; Based on the knob direction, either add or subtract the increment, ; btfsc last_dir,1 ; Is the knob going up? goto up_txo ; Yes, then add the increment down_txo call sub_step ; Subtract fstep from freq goto show_txo up_txo call add_step ; Add fstep to freq call check_add ; Make sure we did not exceed the maximum ; ; * now update TX offset value in toff show_txo call freq_to_toff ; save new offset in toff call bin2BCD call show_freq ; show offset call toff_to_freq ;reestablish free with txo value goto chk_txoffset_chg value_chg_done ; value change been made and put in toff call freq_to_toff ; ; * now check for desired offset sign (+ or -) ; call neg_to_dsp call toff_to_freq call bin2BCD call show_freq call wait_128ms ;debounce delay wait_for_RA4 btfsc PortA,RA4 ;wait for TX to be pressed goto wait_for_RA3 bcf trflags,sgn_txo ;set negative offset flag goto txo_chg_done wait_for_RA3 btfsc PortA,RA3 ;Chg to pos if pressed goto wait_for_RA4 call pos_to_dsp call toff_to_freq call bin2BCD call show_freq bsf trflags,sgn_txo ;set positive offset flag txo_chg_done btfsc PortA,RA4 ;final RA4 low to finish goto txo_chg_done txo_no_chg bsf trflags,init_flag ;txo_set done set init_flag ; Set the power on frequency to the defined value. ; movlw default_0 ; Get the least significant byte movwf freq_0 ; Save it movlw default_1 ; Get the next byte movwf freq_1 ; Save it movlw default_2 ; And the next movwf freq_2 ; Save it movlw default_3 ; Get the most significant byte movwf freq_3 ; Save it ; ; * setup all work freq variables and set RX w/o RIT ; bcf trflags,txo ;turn TX off (ie RX mode now) bcf trflags,rito ; turn RIT OFF call freq_to_ritf ; new freq to RX RIT call freq_to_rxf ; new freq to RX w/o RIT call set_txo_freq ; go set TX freq with RX +/- offset call rxf_to_freq ;show and setup dds for rxf ; ; Display the power on frequency. ; call bin2BCD ; Convert it to BCD call show_freq ; Display it ; ; Send power on frequency to the DDS chip. ; call calc_dds_word ; Convert to delta value call send_dds_word ; Send the power-on frequency to the ; AD9850 in serial mode ; 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 push button switch was 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. This data is transferred to * ; * the AD9850 DDS chip by calling the subroutine send_dds_word. * ; * * ; * If the push button is pressed while turning the encoder then * ; * freq is loaded with a constant stored in band_table. The * ; * variable band is used as an index into the table. Band * ; * is incremented or decremented based on the encoder direction. * ; * * ; * Input: None. * ; * * ; * Output: None. * ; * * ; ***************************************************************************** ; ; main_start ; ******************************************************************** ; * New Tx/RX and RIT Changes Here added by AE4GX 5/20/99 ; ******************************************************************** btfss PortA,RA4 ; is TX (maybe) active flag set? goto chk_RA3 ; yes, is it a RIT toggle request? btfss trflags, txo ; was there a tx on flag set? goto go_chk_encoder ; no, just do old stuff ; ; * Going into Receive mode ; bcf trflags, txo ;clear txo (tx on) flag btfss trflags,rito ;is rit on goto rit_inact ;no use rxf to get *display call ritf_to_freq ;setup ritf in freq goto main_update rit_inact call rxf_to_freq ;setup rxf in fres goto main_update ; ********************************************************************* ; * to get here RA4 was set now chk if RA3 set (if both this means RIT toggle) ; ******************************************************************** chk_RA3 call wait_8ms ;debounce RA4 btfsc PortA,RA4 goto main_start ; not 32ms down maybe noise btfss PortA,RA3 goto rit_toggle ; ;* Going into Transmit Mode ; btfsc trflags, txo ; is TX on active already goto main_start ;keep looking for TX key up condition bsf trflags, txo ; no, set TX on active call txf_to_freq ; move txf to freq goto main_update ; ;* This is an RIT toggle request ; rit_toggle ; Make sure better debounce call wait_8ms ; wait rtn btfsc PortA,RA4 ; RA4 still Low (active)? goto main_start ; no, just retry main_start btfsc PortA,RA3 ; RA3 still Low (active)? goto main_start ; no, just retry main_start call wait_8ms ;wait again for debounce rit_wait_here btfss PortA,RA3 goto rit_wait_here ; wait loop for RIT toggle to go High (inactive) ; ; Must be good RIT Toggle ; btfsc trflags, rito ; is RIT active? goto set_rit_inact ; yes, turn RIT off and reset RX freq active bsf trflags,rito ; no set RIT ON goto set_all set_rit_inact bcf trflags, rito ; turn RIT OFF set_all call rxf_to_freq call freq_to_ritf call set_txo_freq btfss trflags,rito goto set_all2 call ritf_to_freq ;set freq to and show ritf freq goto main_update set_all2 call rxf_to_freq goto main_update ; *************************************************************** ; * end of Tx/RX and RIT MODs ; *************************************************************** go_chk_encoder call poll_encoder ; Check for knob movement btfss PortA,RA4 ;is RA4 low (TX active) goto main_start ;yes, got test for TX and/or RIT chg main_chk_bandchg btfss ren_read,3 ; Change band? goto change_band ; Yes, change band ; ; 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,2 ; Is the encoder switch 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 ; ; * step determined ; main_ck_rit btfss trflags, rito ; is RIT active goto main_ck_rit2 ;no use rxf for freq adjust call ritf_to_freq ; move ritf to freq goto main_ck_rit3 main_ck_rit2 call rxf_to_freq ;move rxf to freq ; ; Based on the knob direction, either add or subtract the increment, ; then update the LCD and DDS. ; main_ck_rit3 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 main_ck_rit4 ; determine which rxfreq var to set up call add_step ; Add fstep to freq call check_add ; Make sure we did not exceed the maximum ; ; * check RIT again to determine which freq to setup ; main_ck_rit4 btfss trflags,rito ; check RIT active again goto main_new_band ; treat new band change all new freqs ; ; * RIT active so just adjust ritf only ; call freq_to_ritf ;move new freq to ritf call ritf_to_freq ; extra setup to get RIT in display goto main_update ; ; ***************************************************************************** ; * * ; * 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 main_new_band ; 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 main_new_band bcf trflags,rito ; turn RIT OFF call freq_to_ritf ; new freq to RX RIT call freq_to_rxf ; new freq to RX w/o RIT call set_txo_freq ; setup TX with RX +/- offset call rxf_to_freq ; set rxf to freq and sets display mode main_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_start ; Continue polling the encoder (endless loop) ; ***************************************************************************** ; * * ; * Purpose: This routine reads the frequency value of a band table entry * ; * pointed to by band and returns it in freq_3...freq_0. * ; * * ; * Input: band must contain the index of the desired band entry * 4 * ; * (with the entries numbered from zero). * ; * * ; * Output: The band frequency in freq. * ; * * ; ***************************************************************************** ; get_band movf band,w ; Get the index of the high byte call band_table ; Get the value into W movwf freq_3 ; Save it in freq_3 incf band,f ; Increment index to next byte movf band,w ; Get the index of the next byte call band_table ; Get the value into W movwf freq_2 ; Save it in freq_2 incf band,f ; Increment index to the next byte movf band,w ; Get the index to the next byte call band_table ; Get the value into W movwf freq_1 ; Save it in freq_1 incf band,f ; Increment index to the low byte movf band,w ; Get the index to the low byte call band_table ; Get the value into W movwf freq_0 ; Save it in freq_0 movlw 0x03 ; Get a constant three subwf band,f ; Restore original value of band return ; Return to the caller ; ; ***************************************************************************** ; * * ; * Purpose: 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 16 X 1 display. * ; * * ; * Input: None * ; * * ; * Output: None * ; * * ; ***************************************************************************** ; init_LCD call wait_64ms ; Wait for LCD to power up movlw 0x30 ; LCD init instruction (First) movwf PortB ; Send to LCD via RB7..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 0x30 ; LCD init instruction (Second) movwf PortB ; Send to LCD via RB7..RB0 bsf PortB,LCD_e ; Set E high, call wait_32ms ; wait a while, bcf PortB,LCD_e ; and then Clear E movlw 0x30 ; LCD init instruction (Third) movwf PortB ; Send to LCD via RB7..RB0 bsf PortB,LCD_e ; Set E high, call wait_32ms ; wait a while, bcf PortB,LCD_e ; and then Clear E movlw 0x20 ; 4-bit mode instruction movwf PortB ; Send to LCD via RB7..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 ; ; ; ***************************************************************************** ; * * ; * 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 movlw 0x40 ; Start with the high bit set in ren_timer movwf ren_timer_0 ; read_encoder btfss trflags,init_flag ;are we checking offset now goto read_on ;yes skip TX active btfss PortA,RA4 ;is TX RA4 Low (TX active) return ;yes,go back to main read_on btfsc trflags,init_flag goto read_on2 btfsc PortA,RA4 ; still in txo chg mode return ;yes must be done read_on2 btfsc ren_timer_1,7 ; Has the bit floated to bottom of ren_timer? goto no_inc ; Yes, don't move it any further movlw 0x08 ; addwf ren_timer_0,f ; 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 andwf ren_read,w ; Isolate encoder bits movwf ren_new ; Save new value xorwf ren_old,w ; Has it changed? btfsc STATUS,B_Z ; goto read_encoder ; No, keep looking until it changes ; ; Determine which direction the encoder turned. ; bcf STATUS,B_C ; Clear the carry bit rlf ren_old,f ; movf ren_new,w ; xorwf ren_old,f ; movf ren_old,w ; andlw 0x02 ; movwf next_dir ; xorwf last_dir,w ; ; ; Prevent encoder slip from giving a false change in direction. ; btfsc STATUS,B_Z ; Zero? goto pe_continue ; No slip; keep going movf next_dir,w ; Yes, update direction movwf last_dir ; movf ren_new,w ; Save the current encoder bits for next time movwf ren_old ; goto read_encoder ; Try again pe_continue btfsc ren_old,1 ; Are we going down? goto up2 ; No, indicate we are going up clrf last_dir ; Yes, clear last_dir goto exit3 ; Finish and return up2 movlw 0x02 ; Set UP value in last_dir movwf last_dir ; exit3 movf ren_new,w ; Get the current encoder bits movwf ren_old ; Save them in ren_old for the next time return ; Return to the caller ; ; ****************************************************************** ; * Common move subrts for TX/RX/RIT changes added by AE4GX 5/20/99 ; ****************************************************************** toff_to_fstep ; Move TX offset value to fstep movf toff_0,w movwf fstep_0 movf toff_1,w movwf fstep_1 movf toff_2,w movwf fstep_2 movf toff_3,w movwf fstep_3 return freq_to_rxf ; Move work freq var. to rxf movf freq_0,w movwf rxf_0 movf freq_1,w movwf rxf_1 movf freq_2,w movwf rxf_2 movf freq_3,w movwf rxf_3 return freq_to_txf ; Move work freq var. to txf movf freq_0,w movwf txf_0 movf freq_1,w movwf txf_1 movf freq_2,w movwf txf_2 movf freq_3,w movwf txf_3 return freq_to_ritf ; Move work freq var. to ritf movf freq_0,w movwf ritf_0 movf freq_1,w movwf ritf_1 movf freq_2,w movwf ritf_2 movf freq_3,w movwf ritf_3 return rxf_to_freq ; Move rxf to work freq movf rxf_0,w movwf freq_0 movf rxf_1,w movwf freq_1 movf rxf_2,w movwf freq_2 movf rxf_3,w movwf freq_3 call rx_to_dsp return ritf_to_freq ; Move ritf to work freq movf ritf_0,w movwf freq_0 movf ritf_1,w movwf freq_1 movf ritf_2,w movwf freq_2 movf ritf_3,w movwf freq_3 call rit_to_dsp return txf_to_freq ; Move txf to work freq movf txf_0,w movwf freq_0 movf txf_1,w movwf freq_1 movf txf_2,w movwf freq_2 movf txf_3,w movwf freq_3 call tx_to_dsp return rx_to_dsp movlw 'R' movwf dsp_0 movlw 'x' movwf dsp_1 movlw ' ' movwf dsp_2 movlw ' ' movwf dsp_3 return rit_to_dsp movlw 'R' movwf dsp_0 movlw 'I' movwf dsp_1 movlw 'T' movwf dsp_2 movlw ' ' movwf dsp_3 return tx_to_dsp movlw 'T' movwf dsp_0 movlw 'x' movwf dsp_1 movlw ' ' movwf dsp_2 movlw ' ' movwf dsp_3 return voff_to_freq movlw voff_0 ; Get the least significant byte movwf freq_0 ; Save it movlw voff_1 ; Get the next byte movwf freq_1 ; Save it movlw voff_2 ; And the next movwf freq_2 ; Save it movlw voff_3 ; Get the most significant byte movwf freq_3 ; Save it movlw 'T' movwf dsp_0 movlw 'X' movwf dsp_1 movlw 'O' movwf dsp_2 movlw ' ' movwf dsp_3 return freq_to_toff movf freq_0,w movwf toff_0 movf freq_1,w movwf toff_1 movf freq_2,w movwf toff_2 movf freq_3,w movwf toff_3 return toff_to_freq movf toff_0,w movwf freq_0 movf toff_1,w movwf freq_1 movf toff_2,w movwf freq_2 movf toff_3,w movwf freq_3 return neg_to_dsp movlw 'N' movwf dsp_0 movlw 'E' movwf dsp_1 movlw 'G' movwf dsp_2 movlw '-' movwf dsp_3 return pos_to_dsp movlw 'P' movwf dsp_0 movlw 'O' movwf dsp_1 movlw 'S' movwf dsp_2 movlw '+' movwf dsp_3 return set_txo_freq call toff_to_fstep ; setup TX offset to calc TX freq btfss trflags,sgn_txo goto set_minus set_plus call add_step ;freq = Rxf + tx offset goto move_newtxf set_minus call sub_step ; freq = Rxf - tx offset move_newtxf call freq_to_txf ; setup new TX freq w/+/- offset return ; *************************************************************** ; * end move subrts ; *************************************************************** ; ***************************************************************************** ; * * ; * Purpose: This routine is entered at start up if the push button is * ; * pressed. "10,000.00 CAL" is displayed on the LCD, and the * ; * the DDS chip is programmed to produce 10 MHz, based on the * ; * osc value stored in the EEPROM. As long as the button is * ; * pressed, the osc value is slowly altered to allow the output * ; * to be trimmed to exactly 10 MHz. Once the encoder is turned * ; * after the button is released, the new osc value is stored in * ; * the EEPROM and normal operation begins. * ; * * ; * Input: The original osc constant in EEPROM * ; * * ; * Output: The corrected osc constant in EEPROM * ; * * ; ***************************************************************************** ; calibrate movlw 0x80 ; Set frequency to 10MHz by movwf freq_0 ; setting freq to the binary equivalent movlw 0x96 ; of 10,000,000. movwf freq_1 ; . movlw 0x98 ; . movwf freq_2 ; . movlw 0x00 ; . movwf freq_3 ; . ; ; Read the starting reference oscillator value form EEPROM. ; clrf EEadr ; Reset the EEPROM read address call read_EEPROM ; Read EEPROM movf EEdata,w ; Get the first osc byte movwf osc_0 ; Save osc frequency call read_EEPROM ; Get next byte movf EEdata,w ; movwf osc_1 ; Save it call read_EEPROM ; Get the third byte movf EEdata,w ; movwf osc_2 ; Save it call read_EEPROM ; Get the fourth byte movf EEdata,w ; movwf osc_3 ; Save it call bin2BCD ; Calculate BCD version of 10,000.00 call show_freq ; Display the frequency on the LCD movlw 0xC4 ; Point LCD at digit 14 movwf LCD_char ; call cmnd2LCD ; movlw 'C' ; Send a C movwf LCD_char ; call data2LCD ; movlw 'A' ; Send an A movwf LCD_char ; call data2LCD ; movlw 'L' ; Send an L movwf LCD_char ; call data2LCD ; cal_loop call calc_dds_word ; Calculate DDS value based on current osc call send_dds_word ; Update the DDS chip call poll_encoder ; Wait until the encoder has moved. clrf fstep_3 ; Clear the three most significant clrf fstep_2 ; bytes of fstep clrf fstep_1 ; movlw 0x10 ; Assume that we are adjusting slowly movwf fstep_0 ; Use small increment btfsc ren_read,2 ; Was the encoder changing slowly? goto update_osc ; Yes, then continue with small increment movlw 0x80 ; No, then use the large increment movwf fstep_0 ; update_osc nop ; Wait a cycle btfsc last_dir,1 ; Are we moving down? goto faster ; No, increase the osc value ; ; slower ; comf fstep_0,f ; Subtraction of fstep is done by comf fstep_1,f ; adding the twos compliment of fsetp comf fstep_2,f ; to osc comf fstep_3,f ; incfsz fstep_0,f ; Increment last byte goto faster ; Non-zero, continue incfsz fstep_1,f ; Increment next byte goto faster ; Non-zero, continue incfsz fstep_2,f ; Increment next byte goto faster ; Non-zero, continue incf fstep_3,f ; Increment the high byte faster movf fstep_0,w ; Get the low byte increment addwf osc_0,f ; Add it to the low osc byte btfss STATUS,B_C ; Was there a carry? goto add4 ; No, add the next bytes incfsz osc_1,f ; Ripple carry up to the next byte goto add4 ; No new carry, add the next bytes incfsz osc_2,f ; Ripple carry up to the next byte goto add4 ; No new carry, add the next bytes incf osc_3,f ; Ripple carry up to the highest byte add4 movf fstep_1,w ; Get the second byte increment addwf osc_1,f ; Add it to the second osc byte btfss STATUS,B_C ; Was there a carry? goto add5 ; No, add the third bytes incfsz osc_2,f ; Ripple carry up to the next byte goto add5 ; No new carry, add the third bytes incf osc_3,f ; Ripple carry up to the highest byte add5 movf fstep_2,w ; Get the third byte increment addwf osc_2,f ; Add it to the third osc byte btfss STATUS,B_C ; Was there a carry? goto add6 ; No, add the fourth bytes incf osc_3,f ; Ripple carry up to the highest byte add6 movf fstep_3,w ; Get the fourth byte increment addwf osc_3,f ; Add it to the fourth byte btfss ren_read,3 ; Is the button still pressed? goto cal_loop ; Yes, stay in calibrate mode clrf EEadr ; Write final value to EEPROM movf osc_0,w ; Record the first movwf EEdata ; osc call write_EEPROM ; byte movf osc_1,w ; Record the second movwf EEdata ; osc call write_EEPROM ; byte movf osc_2,w ; Record the third movwf EEdata ; osc call write_EEPROM ; byte movf osc_3,w ; Record the fourth movwf EEdata ; osc call write_EEPROM ; byte return ; Return to the caller ; ; ***************************************************************************** ; * * ; * Purpose: Multiply the 32 bit number for oscillator frequency times the * ; * 32 bit number for the displayed frequency. * ; * * ; * * ; * Input: The reference oscillator value in osc_3 ... osc_0 and the * ; * current frequency stored in freq_3 ... freq_0. The reference * ; * oscillator value is treated as a fixed point real, with a 24 * ; * bit mantissa. * ; * * ; * Output: The result is stored in AD9850_3 ... AD9850_0. * ; * * ; ***************************************************************************** ; calc_dds_word clrf AD9850_0 ; Clear the AD9850 control word bytes clrf AD9850_1 ; clrf AD9850_2 ; clrf AD9850_3 ; clrf AD9850_4 ; movlw 0x20 ; Set count to 32 (4 osc bytes of 8 bits) movwf mult_count ; Keep running count movf osc_0,w ; Move the four osc bytes movwf osc_temp_0 ; to temporary storage for this multiply movf osc_1,w ; (Don't disturb original osc bytes) movwf osc_temp_1 ; movf osc_2,w ; movwf osc_temp_2 ; movf osc_3,w ; movwf osc_temp_3 ; mult_loop bcf STATUS,B_C ; Start with Carry clear btfss osc_temp_0,0 ; Is bit 0 (Least Significant bit) set? goto noAdd ; No, don't need to add freq term to total movf freq_0,w ; Yes, get the freq_0 term addwf AD9850_1,f ; and add it in to total btfss STATUS,B_C ; Does this addition result in a carry? goto add7 ; No, continue with next freq term incfsz AD9850_2,f ; Yes, add one and check for another carry goto add7 ; No, continue with next freq term incfsz AD9850_3,f ; Yes, add one and check for another carry goto add7 ; No, continue with next freq term incf AD9850_4,f ; Yes, add one and continue add7 movf freq_1,w ; Use the freq_1 term addwf AD9850_2,f ; Add freq term to total in correct position btfss STATUS,B_C ; Does this addition result in a carry? goto add8 ; No, continue with next freq term incfsz AD9850_3,f ; Yes, add one and check for another carry goto add8 ; No, continue with next freq term incf AD9850_4,f ; Yes, add one and continue add8 movf freq_2,w ; Use the freq_2 term addwf AD9850_3,f ; Add freq term to total in correct position btfss STATUS,B_C ; Does this addition result in a carry? goto add9 ; No, continue with next freq term incf AD9850_4,f ; Yes, add one and continue add9 movf freq_3,w ; Use the freq_3 term addwf AD9850_4,f ; Add freq term to total in correct position noAdd rrf AD9850_4,f ; Shift next multiplier bit into position rrf AD9850_3,f ; Rotate bits to right from byte to byte rrf AD9850_2,f ; rrf AD9850_1,f ; rrf AD9850_0,f ; rrf osc_temp_3,f ; Shift next multiplicand bit into position rrf osc_temp_2,f ; Rotate bits to right from byte to byte rrf osc_temp_1,f ; rrf osc_temp_0,f ; decfsz mult_count,f ; One more bit has been done. Are we done? goto mult_loop ; No, go back to use this bit clrf AD9850_4 ; Yes, clear _4. Answer is in bytes _3 .. _0 return ; Done. ; ; ***************************************************************************** ; * * ; * Purpose: This routine sends the AD9850 control word to the DDS chip * ; * using a serial data transfer. * ; * * ; * Input: AD9850_4 ... AD9850_0 * ; * * ; * Output: The DDS chip register is updated. * ; * * ; ***************************************************************************** ; send_dds_word movlw AD9850_0 ; Point FSR at AD9850 movwf FSR ; next_byte movf INDF,w ; movwf byte2send ; movlw 0x08 ; Set counter to 8 movwf bit_count ; next_bit rrf byte2send,f ; Test if next bit is 1 or 0 btfss STATUS,B_C ; Was it zero? goto send0 ; Yes, send zero bsf PortB,7 ; No, send one bsf PortB,5 ; Toggle write clock bcf PortB,5 ; goto break ; send0 bcf PortB,7 ; Send zero bsf PortB,5 ; Toggle write clock bcf PortB,5 ; break decfsz bit_count,f ; Has the whole byte been sent? goto next_bit ; No, keep going. incf FSR,f ; Start the next byte unless finished movlw AD9850_4+1 ; Next byte (past the end) subwf FSR,w ; btfss STATUS,B_C ; goto next_byte ; bsf PortB,0 ; Send load signal to the AD9850 bcf PortB,0 ; 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. * ; * * ; * Input: The values in BCD_4 ... BCD_0 * ; * * ; * Output: The number displayed on the LCD * ; * * ; ***************************************************************************** ; show_freq movlw 0x81 ; Point the LCD to first LCD digit location call cmnd2LCD ; Send starting digit location to LCD ; ; 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 ; movlw ',' ; Get a comma call data2LCD ; Send byte in W to LCD ; 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 ; movlw '.' ; Set up W with ASCII Period call data2LCD ; Send data byte in W to LCD ; movlw 0xC0 ; Point to LCD digit number nine call cmnd2LCD ; Send command byte in W to LCD ; 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 ; movf dsp_0,w ; Send first alpha call data2LCD ; to LCD ; movf dsp_1,w ; Send 2nd alpha call data2LCD ; to LCD ; movf dsp_2,w ; Send 3rd alpha call data2LCD ; to LCD ; movf dsp_3,w ; Send 4th alpha call data2LCD ; to LCD ; 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 * ; * * ; * Output: PortB set as: RB7..RB4 inputs * ; * RB3..RB0 outputs * ; ***************************************************************************** ; busy_check clrf PortB ; Clear all outputs on PortB bsf STATUS,B_RP0 ; Switch to bank 1 for Tristate operation movlw b'11110000' ; Set RB7..RB4 as inputs, RB3..RB0 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 btfsc LCD_read,7 ; Is Busy Flag (RB7) 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 0x0F ; Set up mask andwf PortB,f ; Clear old RB7..RB4 movf LCD_char,w ; Put byte of data into W andlw 0xF0 ; Mask to give XXXX0000 in W iorwf PortB,f ; Send to RB7..RB4 without changing RB3..RB0 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 0x0F ; Set up mask andwf PortB,f ; Clear old RB7..RB4 swapf LCD_char,w ; Move LS nibble of data to MS position in W andlw 0xF0 ; Mask to give YYYY0000 in W iorwf PortB ; Send to RB7..RB4 without changing RB3..RB0 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_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_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