;------------------------------------------------------------------------ ; ----- 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) ; **************************************************************************** ; * PicElGen - Signal Generator (VFO) with Direct Digital Synthesis * ; * Version 2.4 * ; * September 30, 2008 * ; * * ; **************************************************************************** ; Description: ; This is the control program for a DDS VFO built with a PIC16F628 processor, ; a AD9850 or AD9851 DDS chip, a shaft encoder, three push button switches and a ; Hitatchi 44780-compatible Liquid crystal display. The program was originally ; designed to work with PIC-EL and DDS-30 hardware. ; **************************************************************************** ; ; PIC-EL Hardware And Program Configurations ; ;***************************************************************************** ; ; **************************************************************************** ; This program can be used as a Signal Generator or as a Conversion * ; Oscillator for a Superhetrodyne receiver/transmitter. * ; As a signal generator the displayed frequency is what is generated. * ; Default* startup frequency is 200 KHz. * ; The conversion oscillator option adds a Intermediate Frequency offset to * ; the displayed frequency to get the output or conversion frequency. A table * ; of IF frequencies, indexed by band number, is used to obtain the IF offset.* ; The if_table must be edited for the correct IF for each band. * ; Default startup frequency is 3.500 KHz, 80 meters. * ; To enable Conversion Oscillator option (CVFO), uncomment the following * ; #define statement * ; **************************************************************************** ; #define CVFO ; ; ;***************************************************************************** ; Conversion Oscillator may be used in a transmitter. To prohibit out of * ; band operation, band limits may be enabled. Two tables, low_table and * ; high_table are used to check that the frequency is not out of the defined * ; bands. Each table is indexed by band number. low_table has an entry for * ; each band that is the lower frequency limit of the band. Simalirly * ; high_table defines the upper frequency of the band. * ; To enable band limits, uncomment the following define statement. * ;***************************************************************************** ; IFDEF CVFO #define BAND_LIMITS ENDIF ; ;***************************************************************************** ; Conversion Oscillator option default startup band, at power on, is defined * ; in EEPROM location "startup_band" and is band 01 - 3.500 KHZ, 80 Meters. * ; The user may wish to have start up band and frequency as the last band * ; and frequency used before turning power off. This option saves the present * ; band number and frequency periodically and will be default start up band * ; and frequency on the next power up. In other words, VFO acts like a radio * ; and the frequency at power up is the same as when it was turned off. * ; Uncomment the following define statement if this option is desired. * ; * ; count_of_ints is the number of 0.525 second interrupts between saving. * ; * ; Pushbuttons PB1 and PB2 may also be used to change power up band * ;***************************************************************************** ; IFDEF CVFO #define RESTORE_B_F count_of_ints equ 10 ; Interrupts before saving (5.25 seconds)?????????????????????? ENDIF ; ; **************************************************************************** ; LCD Selections. The first batch of AmQRP PIC-EL boards were shipped with * ; 8 character x 1 line LCDs. Later shipments used 16x1 LCDs which are * ; addressed as two 8x1 blocks. To get to the 9th character, the cursor * ; must be explicitly moved. There are other LCDs which employ linear * ; display addressing and no special actions are required to move from the * ; 8th to the 9th character. These displays have been dubbed "linear" for * ; the purposes of this program. The PIC-EL Version 2 uses a 2 line X 16 * ; character linear addressing LCD. A command is required to move to the * ; second line. * ; Uncomment only one of the following 4 #defines for your LCD type * ; **************************************************************************** ;#define LCD_8 ; Original 8 character AmQRP LCD ;#define LCD_16 ; Later AmQRP 16 character LCD ;#define LCD_16L ; Generic 16 character LCD with linear addressing #define LCD_16x2 ; PIC-EL2 16x2 LCD with linear addressing ; **************************************************************************** ; Default frequency display is in KiloHertz for 16 character per line LCD. * ; i.e. - 14,000.000 KHz * ; 8 character per line LCD default is Hertz. * ; This option allows the 16 character LCD to display frequency in Hertz. * ; i.e. - 14,000,000 Hz * ; To enable the option, uncomment the following #define. * ; **************************************************************************** ;#define DISP_HZ ; **************************************************************************** ; Defining DETENT_ENCODER enables software to debounce the PIC-EL mechanical * ; encoder and modifies the incrementing so that only one count changes at * ; each detent. Comment out the line below for optical or detent-less encoders* ; **************************************************************************** #define DETENT_ENCODER ; **************************************************************************** ; Dual VFO's. * ; Enabling this option exacts a price in the PIC-EL board in that it causes * ; RA2 to become an input. This causes Q5 to turn on and the speaker draws * ; current. Both devices get warm. If this option is desired, then it is a * ; good idea to lift one leg of R20 to keep the speaker and Q5 cool. * ; Redesign of the speaker drive circuit eliminates this problem in PIC-EL * ; Version 2. * ; **************************************************************************** #define DUAL_VFO ;***************************************************************************** ; Leading zero suppression of the displayed frequency may be enabled. All * ; leading zeros of the frequency will be not be displayed and character * ; defined by supr_char will be substituted. * ; I.E."00,455,000 Hz" will display as " ,455,000 Hz". * ; To enable leading zero suppression uncomment the following define. * ; **************************************************************************** #define ZERO_SUPR IFDEF ZERO_SUPR supr_char equ ' ' ; Character that replaces suppressed zero ENDIF ;***************************************************************************** ; The following defines the default decade increment (cursor position) at * ; power up. Chose any value between 0 and 8 to customize your version. * ; **************************************************************************** default_decade equ 2 ; **************************************************************************** ; Keep the Version number here * ; **************************************************************************** #define CODE_VERSION "2.4" ; **************************************************************************** ; New Features for Version 2.4 ; ; Option to invoke band limits when used as a Conversion Oscillator. The ; VFO may be used in a transmitter and band limits prohibits out of ; band operation. ; Option to save the band and frequency in EEPROM periodically. The saved ; information is used on the next poewr up to set VFO band and ; frequency. ; ; New Features for Version 2.3: ; ; Expanded the band tables to include 6 meters if using DDS-60 daughter ; card and added Frequency of 200 KHz for Signal generator. Redid ; Amateur Radio bands to start at band beginning (U.S. frequencies). ; Added a Conversion Oscillator option to use in a Hetrodyning receiver/ ; transmitter application. ; If using as a Signal Generator start band is 200 KHz. If using as a ; Conversion Oscillator start band is 80 meters, Band 2. ; Added leading zero suppression of the displayed frequency as an option ; and added option to allow KHz or Hz display for 16 character LCD. ; If LCD display is 2 line X 16 character, show band number and vfo in ; use on second line of display. ; Fixed incorrect delay time in outer_loop. ; ; New Features for Version 2.2: ; ; Added support for the AmQRP DDS-60 daughter card and the AA0ZZ/KangaUS ; PIC-EL Version 2 which uses a PIC16F628 and 2 line X 16 character LCD ; display. ; ; New Features for Version 2.1: ; ; This is the same code base as Version 2.0 which has been adapted ; for the newer PIC16F628A processor. With a few minor changes, it ; will work with the PIC16F627 and PIC16F648A. ; ; ; New Features for Version 2.0: ; ; 1. Push Button Decade Selection. The LCD now displays an underline ; cursor which identifies the lowest frequency digit (as well as ; the higher decades) which will be modified as the shaft encoder ; is turned. ; ; PB1 moves the cursor to the left. PB2 moves the cursor to the right. ; Holding either PB1 or PB2 (but not both) for more than 1 second ; activates the auto repeat feature and the cursor advances automatically ; until the button is released. Repeatability is enhanced and the user ; controls how fast the frequency changes. ; ; A build-time option allows the user to select the default decade ; increment. ; ; 2. Support for the PCI-EL mechanical shaft encoder. A build-time ; option permits the user to enable PIC-EL shaft encoder debounce ; and per-detent frequency changes. This option may also be used ; with optical shaft encoders but the frequency change rate will ; be 1/4 the rate. ; ; 3. Programmable Band Memories. The user is now able to store his/her ; favorite band frequencies in EEPROM. There are 14 Band memories ; available and the concept of operating in a band is introduced. ; By default, each band is programmed with a popular QRP CW ; frequency or popular IF frequency. Of course, any frequency ; may be stored in the band memory. ; ; 4. Programmable Startup Band. The user may program his/her favorite ; startup band. ; ; 5. Dual Independent VFOs. The software supports two VFOs which are ; band and frequency independent. VFO A may be copied to VFO B at ; the user's option. ; ; 6. Support for AmQRP 8 and 16 character LCDs, plus aftermarket linearly ; addressed displays that don't require a "long hop" to character ; position 9. ; ; Further details are presented in the PICgen2.0_release_notes.txt file. ; ; Features retained from Version 1.4: ; ; 1. BAND MEMORIES. A pushbutton switch (PIC-EL PB_2) allows the frequency ; to be cycled around the HF ham bands. ; ; 2. CALIBRATE MODE is entered if 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 or ; PB 3 down while turning. ; ; An external frequency counter on the DDS output is required to observe ; this adjustment. Alternatively, tune a receiver to the 10 MHz WWV ; broadcast and zero-beat the carrier. 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. ; ; ; Features from Version 1.4 not retained: ; ; 1. VARIABLE RATE TUNING. This was removed in favor of selecting the ; decade for updating. ; ; 2. MOVE UP/DOWN 1 MHz. The decade selection expands this option. ; ; 3. SAVE NEW START-UP FREQUENCY. Version 2.0 expands upon this option ; ; ;****************************************************************************** ; 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, AA0ED (sbs1@visi.com) and ; Craig Johnson, AA0ZZ (cbjohns@cbjohns.com) ; ; 10/31/03 PICELgen1.0 Modify for PIC Elmer project by Craig Johnson, AA0ZZ P ; 1) Change to use 1x8 LCD instead of 1x16 P ; - Freq displayed as Hz (e.g. 14025000) P ; - CAL freq displayed as Hz (e.g. 10000000) P ; 2) Set up to support the NJQRP DDS Daughterboard P ; - 50 MHz oscillator P ; 3) Change PORTB pin allocations to support PIC-EL P ; - Change RB4-RB7 to RB0-RB3 P ; - Change RB0 (DDS LOAD) to RB7 P ; - Change RB1 (LCD_rs) to RB6 P ; - Change RB2 (LCD_rw) to RB5 P ; - Change RB3 (LCD_e) to RB4 P ; - Change Busy_check routine to check correct bit P ; - Change cmnd2LCD/data2LCD routine - swap nibblesP ; 4) Change RA2 to always be an LOW output P ; 5) Support pushbutton on RA3 instead of encoder P ; shaft switch for bandswitch and calibrate P ; 6) Support pushbutton on RA4 for fast tuning P ; P ; 1/3/04 PICELgen1.1 Add Title and Version on start-up P ; 1/7/04 PICELgen1.2 Restructure main loop and change_band P ; 2/2/04 PICELgen1.2a Fix confusing comment in the header re shaft sw. P ; Fix comment in header regarding PB_2 for calib. P ; 2/8/04 PICELgen1.3 Fix for reliable startup of DDS P ; Add code for 1 MHz steps (up or down) P ; Add code to save new start-up frequency P ; 3/12/04 PICELgen1.4 Remove use of watchdog timer (temp sensitivity) P ; Add code to support either 1x8 or 1x16 LCDs P ; - Use #DEFINE to select the LCD type P ; Fix calibrate routine so it stays in cal_loop P ; ; ; 4/19/04 PICELgen2.0 Mods by Bob Okas, W3CD as follow: ; ; 1. Added the features listed at the top. ; 2. Fixed PORTA initialization bug that kept RA2 ; as an input, which caused the speaker to ; continuously draw current. ; 3. Added LCD command definitions. ; 4. Put LCD messages in a table to reduce program ; memory usage. Added message display function. ; 5. Modified sub_step to re-complement fstep_3..0 ; 6. Rewrote pushbutton handler. ; 7. Added feature to display a message when ; EEPROM contents are changed. ; 8. Changed default startup frequency to 7040 KHz. ; 9. Removed commented-out legacy code. ; 10. Changed hard-coded constants in program code ; to named constants which appear at the top ; of the program. ; 11. Added support for linearly addressed 16x1 LCDs ; 12. Removed the "#" characters preceeding IF, ELSE ; and ENDIF directives to support other assemblers ; besides MPASM. ; 13. Significant re-writes to optimize program memory ; usage which are too numerous to mention here. ; ; 4/19/04 PICELgen2.01: ; ; 1. Fixed AmQRP 16x1 display and cursor addressing ; bugs. Rearranged some initialization code. W3CD ; ; 10/3/05 PICELgen2.10: ; Adapted for PIC16F628A. W3CD ; ; 12/18/06 PICELgen2.20: ; Adapted for PIC-EL Version 2 and DDS-60. C. Buck Ewing ; ; 4/15/07 PICELgen2.30 ; Added Conversion Oscillator option. Added leading ; zero suppression. When using 2x16 LCD display, VFO ; and band in use are displayed. C. Buck Ewing ; ; 9/30/08 PICELgen2.40 Option to invoke band limits when used as a Conversion ; Oscillator and option to save band and frequency ; through power down and up. C. Buck Ewing ; ; 1/04/2013 PICELgen2.4a ; Initialise DDS with zero frequency at begining of start ; up routine - prevents DDS generating hash. T Mowles VK5TM ; ; ;***************************************************************************** ; * ; Pushbutton Operation * ; * ;***************************************************************************** ; ; PB1 ; If depressed buring power-on, enter Calibration mode. The display shows ; 10,000,000 CAL and 10 MHz signal is generated. The output should be ; monitored with a Frequency Counter, or other means, for correct frequency. ; While holding PB1, the encoder is used to adjust output frequency to ; 10 MHz. Once output frequency is correct, release PB1 and turn encoder. ; Normal operation then starts. It is not necessary to hold PB1 if the ; encoder is not moved allowing entering calibrate mode and waiting for ; thermal stabalization before calibrating. ; ; Momentary depression moves cursor to the left one decade position. The ; cursor will wrap from highest decade to 1 Hz decade. Holding down for ; one second will start automatic movement of cursor to left every 1/8 second. ; ; When held down for 2 seconds, along with PB2, store the current frequency ; at the current band number in EEPROM. The display will show 'UpdatedXX" ; where XX is the current band number. Pushbuttons are then released. ; The frequency stored will be the start frequency when this band is selected. ; ; Used with PB2 to change startup band in EEPROM. ; ; Used with PB3 to copy VFO-A to VFO-B. ; ; PB2 ; Momentary depression moves cursor to the right one decade position. The ; cursor will wrap from the 1 Hz decade to the highest decade. Holding down ; for one second will start automatic movement of cursor to right every ; 1/8 second. ; ; Depressing PB2 and moving encoder before one second will enter band change ; mode. Moving the encoder will cycle through all the bands configured and ; releasing PB2 will select last band displayed. ; ; While in band change mode, PB2 still depressed, PB1 is pressed and both ; are held for 2 seconds, the current band number is written to EEPROM as the ; power-up band. Display wil show "Updated" for one second and then "Initband" ; for one second. Both pushbuttons are then released. ; ; PB3 ; PB3 is only functional if DUAL VFO is defined. ; Momentary depressing PB3 will switch vfo's. ; ; Holding PB3 and then momentary depressing PB1 will copy VFO-A to VFO-B. ; Band number and frequency is copied from VFO-A to VFO-B. ; ;***************************************************************************** ; ;***************************************************************************** ; P ; Target Controller - PIC16F628 in PIC-EL board or PIC16F628A P ; __________ P ; PB_3-Speaker----RA2 |1 18| RA1---------ENCODER A P ; PB_2-Bandswitch-RA3 |2 17| RA0---------ENCODER B P ; PB_1-Tuning etc-RA4 |3 16| OSC1--------XTAL P ; +5V-----------!MCLR |4 15| OSC2--------XTAL P ; Ground----------Vss |5 14| VDD---------+5 V P ; LCD11-----------RB0 |6 13| RB7---------DDS_LOAD P ; LCD12-----------RB1 |7 12| RB6---------LCD_rs (LCD Pin 4) P ; LCD13/DDS_CLK---RB2 |8 11| RB5---------LCD_rw (LCD Pin 5) P ; LCD14/DDS_DATA--RB3 |9 10| RB4---------LCD_e (LCD Pin 6) P ; ---------- P ; P ; **************************************************************************** ; * Device type and options. * ; **************************************************************************** ; processor PIC16F628A #include P16f628A.inc ; Include register and memory definitions radix dec errorlevel -302 ; Skip out of bank nuisance messages ; ; **************************************************************************** ; * Configuration fuse information: * ; **************************************************************************** __config _CP_OFF&_LVP_OFF&_BODEN_OFF&_MCLRE_ON&_PWRTE_ON&_WDT_OFF&_INTOSC_OSC_NOCLKOUT ; ************************************************************************ ; ; **************************************************************************** ; * ID location information: * ; * * ; **************************************************************************** ; __idlocs 0xBE24 ; ; ***************************************************************************** ; * DDS Frequency control equates. These may be changed to accommodate the * ; * reference clock frequency and the desired upper frequency limit 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 clock 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 / clock_freq_in_Hertz). ; ref_osc_2, ref_osc_1, and ref_osc_0 are the fractional part of ; (2^32 / clock_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 ; #ifdef AD9850 ; For 125 MHz Oscillator ======= ref_osc_3 equ 0x22 ; Most significant osc byte ref_osc_2 equ 0x5C ; Next byte ref_osc_1 equ 0x17 ; Next byte ref_osc_0 equ 0xD0 ; Least significant byte #endif ;#ifdef 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 Oscillator and 6x multiplier) For DDS-60 ref_osc_3 equ 0x17 ; Most significant osc byte ref_osc_2 equ 0xDC ; Next byte ref_osc_1 equ 0x65 ; Next byte ref_osc_0 equ 0xDE ; Least significant byte #endif ; limit3..0 specify the upper limit frequency (in Hz) expressed 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 0x02 ; Most significant byte for 40 MHz limit_2 equ 0x62 ; Next byte limit_1 equ 0x5A ; Next byte limit_0 equ 0x00 ; Least significant byte #endif #ifdef AD9851 limit_3 equ 0x03 ; Most significant byte for 60 MHz limit_2 equ 0x93 ; Next byte limit_1 equ 0x87 ; Next byte limit_0 equ 0x00 ; Least significant byte #endif ; The following is the definition of the 10.00000 MHz ; calibration frequency cal_freq_0 equ 0x80 ; LSB cal_freq_1 equ 0x96 cal_freq_2 equ 0x98 cal_freq_3 equ 0x00 ; MSB ;************************************************************************************* ; ; General definitions ; ;************************************************************************************* EEPROM_BASE equ 0x2100 ; Where EEPROM begins in the 16F628A RAM_BASE equ 0x20 ; Where General Purpose Regs. begin in 16F628A RAM_ALL equ 0x70 ; Origin in BANK 0 of ram accessable across all pages TABLE_ADJUST equ 3 ; Value to adjust table pointers MSB equ 7 ; Most significant bit in a byte UP_BIT equ 0x02 ; Used by poll_encoder to determine direction DIR_BIT equ 1 ; Bit to test for shaft encoder direction FLAG_MASK equ 0xff ; Mask used to set status flags. BAND_ADJUST equ -1 ; Added to vfo_pointer contents to get vfo_X_band LOW_NYBBLE_MASK equ 0x0f ; Mask used to extract low 4 bits in a byte HI_NYBBLE_MASK equ 0xf0 ; Mask used to extract high 4 bits in a byte ASCII_NUM_BASE equ 0x30 ; Added to BCD digits to get printable digits ENCODER_BITS equ 0x03 ; Mask applied to PORTA to extract encoder position TEN equ 10 ; VFO_SEL equ 0 ; VFO A/B select bit FREQ_COUNT equ 4 ; Number of frequency bytes to copy DECADE_MASK equ 0x07 ; Applied to decade adjustments SMALL_FSTEP equ 0x05 ; Small cal. freq. step. About .4 Hz LARGE_FSTEP equ 0x80 ; Large cal. freq. step. About 3 Hz IFDEF DUAL_VFO PORTA_CONTROL equ 0xff ; Port A is all inputs for Dual VFOs ELSE PORTA_CONTROL equ 0xfb ; Port A is all inputs except RA2 ENDIF T1CON_INIT equ 0x31 ; Timer1 - Prescale of 8 and enable the timer INTCON_INIT equ 0xC0 ; Interrupt Control- Enable Global and Peripheral ;************************************************************************************** ; PB_flags bits and related definitions ; ; These bits define the operation of PB_1 and PB_2 during startup and normal ; operations. ; ;************************************************************************************** PB_RPT_FLG equ 0 ; Indicates that PB was repeating PCAL_FLG equ 1 ; Indicates that cal mode is enabled PB_1_DLY equ 2 ; Indicates that PB_1 repeat delay countdown is active PB_2_DLY equ 3 ; Indicates that PB_2 repeat delay countdown is active BAND_MODE equ 4 ; Indicates that Encoder was turned while PB_2 is pressed VFO_ACTION equ 5 ; Indicates that PB3 was pressed and that a vfo action ; was requested. DECADE_DECREMENT equ 0xff ; Value which decrements cursor frequency decade DECADE_INCREMENT equ 0x01 ; Value which increments cursor frequency decade PB_RPT_WAIT equ 32 ; 1 second delay before autorepeat PB_RPT_DLY equ 4 ; 1/8 second between repeats PB_INIT_FREQ_WAIT equ 64 ; 2 second delay before new start freq set IFDEF RESTORE_B_F ;*****************************************************************************************?????????? ; Interrupt Processing Flags ;***************************************************************************************** T1_INT equ 0 ; Indicates a Timer 1 interrupt occurred so the interrupt ; can be processed FIRST_TIME equ 1 ; Indicates that first time through power up processing ; has been completed POWER_UP equ 2 ; Indicates processing power up ENDIF ; ;****************************************************************************** ; * ; The following definitions depend upon the #define LCD selection above. * ; * ;****************************************************************************** IFDEF LCD_8 ; AmQRP 8x1 LCDs -- Shipped with earlier kits #define LCDCHAR 8 ; Enable 8-character LCD program features ENDIF IFDEF LCD_16 ; AmQRP 16x1 LCDs -- Shipped with later kits #define LCDCHAR 16 ; Enable 16-character LCD program features #define LCD16_LINEAR 0 ; Use for AmQRP 16x1 displays ENDIF IFDEF LCD_16L ; Alternative, linearly addressed 16x1 displays #define LCDCHAR 16 ; Enable 16-character LCD program features #define LCD16_LINEAR 1 ; Use linear addressing ENDIF IFDEF LCD_16x2 ; PIC-EL 2 16x2 LCD linear addressing display #define LCDCHAR 16 ; Enable 16-character LCD program features #define LCD16_LINEAR 2 ; It is linear addressing, but two lines ENDIF ;***************************************************************************** ; ; LCD Command Definitions ; ; The following are a series of command definitions and parameters that the ; Hitatchi 44780 LCD controller chip recognizes. Some commands have parameters ; which may be or'ed into the basic data to form a single-byte command. In ; such cases, these parameters have been grouped directly underneath the basic ; command data and bear descriptive names and comments related to their ; functions. ; ;***************************************************************************** CMD_CLEAR_LCD equ 0x01 ; Clears the Display CMD_RESET_LCD equ 0x03 ; Resets the display. Write this 3 times CMD_SET_IFC_1 equ 0x02 ; Interface programming word 1 IFC1_4_BIT equ 0x00 ; 4 bit I/O with LCD IFC1_8_BIT equ 0x01 ; 8-bit I/O (not used with PIC-EL) PICEL_IFC1 equ CMD_SET_IFC_1 + IFC1_4_BIT CMD_SET_IFC_2 equ 0x20 ; Interface programming word 2 IFC2_4_BIT equ 0x00 ; 4-bit I/O IFC2_8_BIT equ 0x10 ; 8-bit I/O (not used with PIC-EL) IFC2_1_LINES equ 0x00 ; Set display to 1 line of characters IFC2_2_LINES equ 0x08 ; Set display to 2 lines of characters IFC2_FONT_5_7 equ 0x00 ; Sets display font to 5x7 dot character matrix IFC2_FONT_5_10 equ 0x04 ; Sets display font to 5x10 dot character matrix IF LCDCHAR == 16 IF LCD16_LINEAR == 0 ; Later AmQRP 16 character LCD PICEL_IFC2 equ CMD_SET_IFC_2 + IFC2_4_BIT + IFC2_2_LINES + IFC2_FONT_5_7 ELSE IF LCD16_LINEAR == 1 ; Generic 16 character LCD with linear addressing PICEL_IFC2 equ CMD_SET_IFC_2 + IFC2_4_BIT + IFC2_1_LINES + IFC2_FONT_5_7 ELSE IF LCD16_LINEAR == 2 ; PIC-EL2 16x2 LCD with linear addressing PICEL_IFC2 equ CMD_SET_IFC_2 + IFC2_4_BIT + IFC2_2_LINES + IFC2_FONT_5_7 ENDIF ENDIF ENDIF ENDIF IF LCDCHAR == 8 ; Original 8 character AmQRP LCD PICEL_IFC2 equ CMD_SET_IFC_2 + IFC2_4_BIT + IFC2_2_LINES + IFC2_FONT_5_7 ENDIF CMD_DISP_CUR_EN equ 0x08 ; Base command for display & cursor control DISP_OFF equ 0x00 ; Disables display DISP_ON equ 0x04 ; Enables display CUR_OFF equ 0x00 ; Disables cursor CUR_ON equ 0x02 ; Enables underline cursor BLINK_OFF equ 0x00 ; Disables blinking block cursor BLINK_ON equ 0x01 ; Enables blinking block cursor PICEL_DISPLAY_OFF equ CMD_DISP_CUR_EN + DISP_OFF + CUR_OFF + BLINK_OFF PICEL_DISPLAY_ON equ CMD_DISP_CUR_EN + DISP_ON + CUR_ON CMD_HOME_CUR_LCD equ 0x02 ; Return Display and cursor to home pos. CMD_SET_INC equ 0x04 ; Sets Display and cursor movement INC_DISPLAY_POS equ 0x01 ; Shifts the display after data write INC_CURSOR_POS equ 0x02 ; Increments the cursor along with display PICEL_CURSOR_RT equ CMD_SET_INC + INC_CURSOR_POS CMD_SET_DSP_CUR equ 0x08 ; Controls Display and cursor ENABLE_BLOCK_BLINK equ 0x01 ; Blinking Block if enabled ENABLE_CURSOR equ 0x02 ; Underline if not blinking block ENABLE_DISPLAY equ 0x04 ; Turns on Display CMD_SET_SCROLL equ 0x10 ; Controls Display/Cursor Scrolling SCROLL_ENABLE equ 0x08 ; Enables Display/Cursor Scrolling SCROLL_RIGHT equ 0x04 ; Enables Right scroll. Otherwise it's Left. CMD_CURSOR_CGRAM equ 0x40 ; Move cursor position within character ; generator memory. "OR" in the CGRAM ; address to this command. Valid range ; is 0x00 - 0x3f CMD_MOV_CUR_DISP equ 0x80 ; Move cursor position within Display ; "OR" in the Display position to this command. ; 0x00 is the leftmost position. OFFSET_9 equ 0x40 ; Offset added to above command to position ; cursor at 9th character. Used for AmQRP ; 16 character LCDs. ; Or, move to 2nd line of 16x2 LCD PICEL_LONG_9 equ CMD_MOV_CUR_DISP + OFFSET_9 LCD_POS_9 equ 8 ; Cursor position 9 on 16 character LCDs LCD_POS_10 equ 9 ; Cursor position 10 on 16 character LCDs ; ; **************************************************************************** ; * Assign names to IO pins. * ; **************************************************************************** ; ; B register bits: ; DDS_clk equ 0x02 ; AD9850 write clock DDS_dat equ 0x03 ; AD9850 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 ; ; A register bits: ; SPEAKER equ 0x02 ; Speaker (always exit with a low output) P PB_3 equ 0x02 ; PB also on this pin P PB_2 equ 0x03 ; Bandswitch Pushbutton, P PB_1 equ 0x04 ; Tuning-increment/Calibrate Pushbutton P ; **************************************************************************** ; * Setup the initial constant, based on the frequency of the reference * ; * oscillator. This can be tweaked with the calibrate function. * ; **************************************************************************** ; ORG EEPROM_BASE ; ref_osc bytes appear as the first 4 bytes of EEPROM DATA ref_osc_0 DATA ref_osc_1 DATA ref_osc_2 DATA ref_osc_3 startup_loc startup_band equ startup_loc - EEPROM_BASE IFDEF CVFO DATA D'06' ; Conversion Oscillator band ; 8950 USES ONE IF NUMBER LESS ENDIF IFNDEF CVFO IFDEF DDS60 DATA D'11' ; Signal generator startup band ELSE DATA D'10' ENDIF ENDIF IFDEF RESTORE_B_F ;??????????????????????????????????????????????????????????????????????????? restore_loc_a restore_band_a equ restore_loc_a - EEPROM_BASE DATA 0x00 restore_loc_b restore_band_b equ restore_loc_b - EEPROM_BASE DATA 0x00 restore_freq_a restore_f_a equ restore_freq_a - EEPROM_BASE DATA 0x00, 0x00, 0x00, 0x00 ; VFO-A start frequency save locations restore_freq_b restore_f_b equ restore_freq_b - EEPROM_BASE DATA 0x00, 0x00, 0x00, 0x00 ; VFO-B start frequency save locations restore_flag_loc restore_flag equ restore_flag_loc - EEPROM_BASE DATA 0x00 ; Flag save to indicate been thru power up once ENDIF ;***************************************************************************** ; ; Programmable Band Memories. The following EEPROM locations store ; the user-programmed frequencies of their choice. The notion of ; bands is a loose one since any frequency can be stored here. The ; program defaults are the U.S. Amateur Band start frequencies for ; each band, popular IF frequencies and a "Signal Generator" frequency. ; Data is frequency, in Hz, expressed in hexadecimal and is stored ; LSB first. ; ;***************************************************************************** band_loc band_table equ band_loc - EEPROM_BASE DATA 0x40, 0x77, 0x1b, 0x00 ; Band 0 1.800 MHz DATA 0xe0, 0x67, 0x35, 0x00 ; Band 1 3.500 MHz DATA 0xc0, 0xcf, 0x6a, 0x00 ; Band 2 7.000 MHz DATA 0x20, 0x1D, 0x9A, 0x00 ; Band 3 10.1 MHz DATA 0x80, 0x9f, 0xd5, 0x00 ; Band 4 14.000 MHz DATA 0x20, 0xB2, 0x13, 0x01 ; Band 5 18.068 MHz DATA 0x40, 0x6f, 0x40, 0x01 ; Band 6 21.000 MHz DATA 0x90, 0xCA, 0x7B, 0x01 ; Band 7 24.890 MHz DATA 0x00, 0x3f, 0xab, 0x01 ; Band 8 28.000 MHz DATA 0x00, 0x00, 0x00, 0x00 ; Band 9 00000000 Gen Coverage ;DATA 0x80, 0xf0, 0xfa, 0x02 ; Band 9 50.000 Mhz ; ; If using a 8950, the band numbers below will be one less than listed ; IFNDEF CVFO ; Some popular IF frequencies ; DATA 0x40, 0x54, 0x89, 0x00 ; Band 7 9 MHz - ssb rig DATA 0x38, 0xff, 0x4a, 0x00 ; Band 8 4.915 MHz IF - QRP rigs DATA 0x58, 0xf1, 0x06, 0x00 ; Band 9 455 KHz - Lots!!! DATA 0xb8, 0xcd, 0x33, 0x00 ; Band 10 3.395 MHz - Heathkit SB series ; ; A Signal Generator ; DATA 0x40, 0x0d, 0x03, 0x00 ; Band 11 200 KHz - Signal Generator ENDIF band_end_loc num_bands equ (band_end_loc - band_loc) / 4 ; ; **************************************************************************** ; * Allocate variables in general purpose register space * ; **************************************************************************** ; CBLOCK RAM_BASE ; Start Data Block freq_0 ; Frequency (hex) freq_1 ; (4 bytes) freq_2 ; Used by bin2BCD and calc_dds_word freq_3 fstep_0 ; Frequency inc/dec fstep_1 ; (4 bytes) fstep_2 ; update_decade fills these in fstep_3 ; with values from decade_table ioff_0 ; IF offset LSByte ioff_1 ; (4 bytes) ioff_2 ; Used by calc_dds_word ioff_3 band_low_0 ; Storage for band start frequency band_low_1 ; Used in low frequency test band_low_2 band_low_3 band_high_0 ; Storage for band upper frequency limit band_high_1 ; Used in upper frequency test band_high_2 band_high_3 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 ; Used by calibrate osc_temp_3 BCD_0 ; Display frequency (BCD) BCD_1 ; (5 bytes) BCD_2 BCD_3 BCD_4 AD9850_0 ; AD9850 or AD9851 control word AD9850_1 ; (5 bytes) AD9850_2 AD9850_3 AD9850_4 BCD_count ; Used in bin2BCD routine BCD_temp ; " mult_count ; Used in calc_dds_word bit_count ; " byte2send ; LCD_char ; Character being sent to the LCD LCD_read ; Character read from the LCD timer1 ; Used in delay routines timer2 ; " ren_new ; New value of encoder pins A and B ren_old ; Old value of encoder pins A and B ren_read ; Encoder pins A and B and switches last_dir ; Indicates last direction of encoder next_dir ; Indicates expected direction count ; Multipurpose loop counter rs_value ; The LCD rs line flag value PB_flags ; Pushbutton flags PB_wait_count ; PB repeat control timer temp ; Miscellaneous variable decade ; Frequency decade subject to updating cur_pos ; Cursor position: 0-7 for 8 char LCD message_pointer ; Temp message pointer variable enc_counter ; Divide by 4 counter for mechanical encoder vfo_select ; A = 0x00, B = 0x01 ; N.B. Don't disturb the order of the VFO ; variables as their adjacency is required. vfo_a_band ; Stores the current VFO A band vfo_a_0 ; Used to store the frequency setting vfo_a_1 ; of VFO A when B is in use vfo_a_2 vfo_a_3 vfo_b_band ; Current VFO B band storage vfo_b_0 ; Used to store the frequency setting vfo_b_1 ; of VFO B. vfo_b_2 vfo_b_3 vfo_pointer ; pointer to current vfo_X_0. band_index ; Band in use band number*4 ito ndex into tables supr_test ; Used in show_freq to indicate non-zero character int_counter ; Used to count number of interrupts int_flags ; Flags for power up and interrupt processing ENDC ; End of Data Block CBLOCK RAM_ALL ; Common RAM base ee_addr ; Storage for eeprom address save_w ; Save "W" on interrupt save_s ; Save "STATUS" on interrupt save_fsr ; Save "FSR" on interrupt ENDC ; ; **************************************************************************** ; * The 16F628 resets to 0x00. * ; * The Interrupt vector is at 0x04. (Unused) * ; **************************************************************************** ; ORG 0x0000 reset_entry goto start ; Jump around tables to main program ORG 0x0004 interrupt_entry IFDEF RESTORE_B_F ;??????????????????????????????????????????????????????????????????????????? nop ; Required to fall through interrupt vector goto interrupt ; Go to interrupt routine ENDIF IFDEF BAND_LIMITS ; ; **************************************************************************** ; * Programmable Band Limit Tables - Conversion Option * ; * * ; * The tables contain the first and last frequency of each band. They are * ; * used to test that the frequency has not exceeded the lower and upper * ; * band limits. * ; * The tables are in the same order as the band_table. Frequency is Hz, * ; * expressed in hexadecimal, Least Significant Byte first. * ; * * ; * NOTE: If band_table is changed, these tables MUST also be changed * ; * * ; **************************************************************************** ; low_table addwf PCL,f DT 0x40, 0x77, 0x1b, 0x00 ; Band 0 1.800 MHz - 160 Mteres DT 0xe0, 0x67, 0x35, 0x00 ; Band 1 3.500 MHz - 80 Meters DT 0xc0, 0xcf, 0x6a, 0x00 ; Band 2 7.000 MHz - 40 Meters DT 0x20, 0x1D, 0x9A, 0x00 ; Band 3 10.100 MHz - 30 meters DT 0x80, 0x9f, 0xd5, 0x00 ; Band 4 14.000 MHz - 20 Meters DT 0x20, 0xB2, 0x13, 0x01 ; Band 5 18.068 MHz - 17 meters DT 0x40, 0x6f, 0x40, 0x01 ; Band 6 21.000 MHz - 15 Meters DT 0x90, 0xCA, 0x7B, 0x01 ; Band 7 24.890 MHz - 12 meters DT 0x00, 0x3f, 0xab, 0x01 ; Band 8 28.000 MHz - 10 Meters DT 0x00, 0x00, 0x00, 0x00 ; Band 9 00000000 - General overage ;DT 0x80, 0xf0, 0xfa, 0x02 ; Band 10 50.000 Mhz - 6 Meters ;**************************************************************************** high_table addwf PCL,f dt 0x80, 0x84, 0x1E, 0x00 ; Band 0 2.000 Mhz - 160 Meters dt 0xC0, 0xFB, 0x39, 0x00 ; Band 1 3.800 MHz - 80 Meters dt 0x00, 0xDD, 0x6D, 0x00 ; Band 2 7.200 MHz - 40 Meters DT 0x70, 0xE0, 0x9A, 0x00 ; Band 3 10.150 MHz - 30 meters dt 0xB0, 0xF6, 0xDA, 0x00 ; Band 4 14.350 MHZ - 20 Meters DT 0xC0, 0x38, 0x15, 0x01 ; Band 5 18.168 MHz - 17 meters dt 0x10, 0x4D, 0x47, 0x01 ; Band 6 21.450 MHz - 15 Meters DT 0x30, 0x51, 0x7D, 0x01 ; Band 7 24.990 MHz - 12 meters dt 0x80, 0xC3, 0xC9, 0x01 ; Band 8 30.000 MHz - 10 Meters DT 0x00, 0x5A, 0x62, 0x02 ; Band 9 40.000 Mhz Gen Coverage end ;DT 0x80, 0xF9, 0x37, 0x03 ; Band 10 54.000 MHz - 6 Meters ENDIF IFDEF CVFO ; **************************************************************************** ; **************************************************************************** ; * IF Offset Table * ; * * ; * There are many ways to implement modifying the Generated Frequency * ; * to account for the Intermediate Frequency of a receiver. The method * ; * chosen allows for different IF on each band and positive or negative * ; * IF offset. * ; * Instructions are IF offset frequency in Hz, expressed in Hexadecimal, * ; * LSByte first. * ; * The value for each band is added to the displayed receiver frequency * ; * to get actual output frequency for the VFO (Conversion Oscillator). * ; * If the IF offset is below the desired frequency, negate the offset * ; * and convert to hexadecimal. * ; * Example: DDS output is 3.035 MHz below receive frequency for 6 meters, * ; * Band 6. i.e. -3035000 and convert to hexadecimal 0xFFD1B088 * ; * * ; * This table must be edited for the IF Frequency and postive or negative * ; * offset required. If the band table is changed this table must also * ; * be modified. * ; * * ; * Table is an example used for a Hammerlund HQ-110 receiver. * ; * * ; **************************************************************************** if_table addwf PCL,f dt 0x40, 0x54, 0x89, 0x00 ; + 9 MHz IF, 160 meters Band 0 dt 0x40, 0x54, 0x89, 0x00 ; + 9 MHz IF, 80 meters Band 1 dt 0x40, 0x54, 0x89, 0x00 ; + 9 MHz IF, 40 meters Band 2 dt 0x40, 0x54, 0x89, 0x00 ; + 9 MHz IF, 30 meters Band 3 dt 0x40, 0x54, 0x89, 0x00 ; + 9 MHz IF, 20 meters Band 4 dt 0x40, 0x54, 0x89, 0x00 ; + 9 MHz IF, 17 meters Band 5 dt 0x40, 0x54, 0x89, 0x00 ; + 9 MHz IF, 15 meters Band 6 dt 0x40, 0x54, 0x89, 0x00 ; + 9 MHz IF, 12 meters Band 7 dt 0x40, 0x54, 0x89, 0x00 ; + 9 MHz IF, 10 meters Band 8 dt 0x40, 0x54, 0x89, 0x00 ; + 9 MHz IF 00000000 ;dt 0x40, 0x54, 0x89, 0x00 ; + 9 MHz IF, 6 meters Band 9 ;**************************************************************************** ENDIF ;**************************************************************************** ; ; Decade Table ; ; Each entry is four instructions long, with each group of four literals ; representing the frequency as a 32 bit integer. Each four byte entry ; represents the decade increment which can be assigned to ; fstep_3, fstep_2, fstep_1 and fstep_0. Data is stored MSB first. ; ;**************************************************************************** decade_table addwf PCL,f ; Jump to the appropriate value to return dt 0x00, 0x00, 0x00, 0x01 ; 1Hz increment dt 0x00, 0x00, 0x00, 0x0a ; 10 Hz increment dt 0x00, 0x00, 0x00, 0x64 ; 100 Hz increment dt 0x00, 0x00, 0x03, 0xe8 ; 1 KHz increment dt 0x00, 0x00, 0x27, 0x10 ; 10 KHz increment dt 0x00, 0x01, 0x86, 0xa0 ; 100 KHz increment dt 0x00, 0x0f, 0x42, 0x40 ; 1 MHz increment dt 0x00, 0x98, 0x96, 0x80 ; 10 MHz increment ;**************************************************************************** ; ; Cursor Positioning Table ; ; 8 and 16 character LCD displays differ, so cursor positioning for ; frequency displays is put into tables to simplify the software. ; ;**************************************************************************** cursor_table addwf PCL,f ; Jump to the appropriate value to return IF LCDCHAR == 8 dt 7, 6, 5, 4, 3, 2, 1, 0 ELSE dt 10, 9, 8, 6, 5, 4, 2, 1 ENDIF ;**************************************************************************** ; ; Message Table. All of the PIC-EL LCD messages are stored here. The last ; byte of the message string is set to 0 to indicate the end of string. The ; messages are indexed via an offset value, which is calculated at assembly ; time. ; ;**************************************************************************** message_table addwf PCL,f ; Calculate the return value address ; and jump to the appropriate character messages sign_on_msg ; Displayed at power-on dt "PICELGen", 0 sign_msg_offset equ sign_on_msg - messages ver_msg ; Displayed at power-on IF LCDCHAR == 8 dt "Ver", CODE_VERSION, 0 ELSE dt " Ver ", CODE_VERSION, 0 ENDIF ver_msg_offset equ ver_msg - messages band_msg ; Displayed during band changes and dt "Band ", 0 ; Startup band writes band_msg_offset equ band_msg - messages update_msg ; Displayed during EEPROM writes dt "Update", 0 upd_msg_offset equ update_msg - messages init_msg ; Displayed during startup band writes dt "InitBand", 0 init_msg_offset equ init_msg - messages IF LCDCHAR == 16 IFDEF DISP_HZ hz_msg dt " Hz",0 ; Hz display for 16 char lcd only hz_offset equ hz_msg - messages ELSE khz_msg dt " KHz",0 ; Kilohertz message for 16 char LCD khz_offset equ khz_msg - messages ENDIF calibrate_msg ; Displayed during CAL mode only dt "CAL ", 0 ; and only on 16 character LCDs cal_offset equ calibrate_msg - messages vfoa_msg ; Displayed on 16 x 2 LCD's only dt "VFO-A", 0 ; VFO A selected message vfoa_offset equ vfoa_msg - messages vfob_msg ; Displayed on 16 x 2 LCD's only dt "VFO-B", 0 ; VFO B selected message vfob_offset equ vfob_msg - messages ENDIF IFDEF RESTORE_B_F ;??????????????????????????????????????????????????????????????????????????? ;****************************************************************************** ; * ; Interrupt routine for Timer 1 (Only interrupt enabled). * ; Saves the registers, stops timer, sets the flag bit to inform poll_encoder, * ; resets the interrupt flag, sets Timer1 for maximum count, starts timer, * ; restores registers and exits. * ; * ;****************************************************************************** interrupt movwf save_w ; Store W in save_w swapf STATUS,w ; Move STATUS to W clrf STATUS ; Force to Bank 0 movwf save_s ; Store STATUS in save_s movf FSR,w ; Move FSR into W movwf save_fsr ; Save FSR clrf T1CON ; Stop the timer by clearing T1CON bsf int_flags,T1_INT ; Set the flag for poll_encoder bcf PIR1,TMR1IF ; Reset the interrupt flag clrf TMR1L ; Reload Timer 1 Low clrf TMR1H ; and Timer 1 High movlw T1CON_INIT ; Restart Timer 1 with prescale = 8 movwf T1CON ; Timer 1 control movf save_fsr,w ; Get saved FSR movwf FSR ; Restore the FSR swapf save_s,w ; Get saved status movwf STATUS ; Restore STATUS register (now in pre-interrupt Bank) swapf save_w,f ; swapf save_w,w ; Restore W register retfie ; Return to interrupted location ENDIF ; ; ***************************************************************************** ; * * ; * 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 startup_band * ; * definition 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 movlw 0x07 ; Set the 3 LSBs of CMCON movwf CMCON ; to enable normal I/O clrf RCSTA ; Disable USART Receiver banksel TXSTA ; Switch to bank 1 clrf TXSTA ; Disable USART Transmitter clrf PIE1 ; Clear all interrupt enable bits clrf VRCON ; Disable voltage reference bcf OPTION_REG,NOT_RBPU ; Enable weak pullups movlw PORTA_CONTROL ; Set Port A bits. movwf TRISA ; clrf TRISB ; Set port B to all outputs banksel PORTA ; Switch back to bank 0 ; Initialize DDS Module with zero freq clrf AD9850_0 ; AD9850/51 control word clrf AD9850_1 ; (5 bytes) clrf AD9850_2 clrf AD9850_3 clrf AD9850_4 call send_dds_word1 ; Send it to the DDS call send_dds_word1 ; twice to be sure IFNDEF DUAL_VFO ; Move here to really clear spkr bit - V2.0 bcf PORTA,SPEAKER ; Set speaker output low (don't use here) ENDIF call init_LCD ; Initialize the LCD call display_version ; Display title and version number ; ; Read reference oscillator values from EEPROM ; movlw osc_0 ; Get osc_0 address movwf FSR ; save for indirect addressing clrf ee_addr ; Osc values are at EEPROM start call read_EEPROM_freq ; Read the EEPROM and save in osc bytes ; ; Initialize other variables. ; clrf last_dir ; Clear the knob direction indicator clrf PB_flags ; Clear the flags clrf int_flags ; Clear interrupt flags ; ; Get the power on encoder value. ; movf PORTA,w ; Read port A movwf ren_read ; Save it in ren_read movlw ENCODER_BITS ; Get encoder mask (RA0 and RA1) andwf ren_read,w ; Get encoder bits movwf ren_old ; Save in ren_old ; ; Select VFO A at startup and establish pointer ; clrf vfo_select ; Start up with VFO A movlw vfo_a_0 ; Get address of VFO A registers movwf vfo_pointer ; save in pointer register ; ; Enter Calibrate Mode if push button is pressed while turning the ; power on. ; btfss ren_read,PB_1 ; Tuning-increment/Cal pushbutton pressed? call calibrate ; Yes, calibrate ; Else, continue with rest of initialization IFDEF RESTORE_B_F ;??????????????????????????????????????????????????????????????????????????? ; ; If restore band and frequency are defined: Determine if this is very first time ; through start up. If it is use the default band and frequency. If not use the ; restore information saved in EEPROM. ; bsf int_flags,POWER_UP ; Set power up flag bit clrf temp movlw restore_flag ; Point to restore_flag in EEPROM movwf ee_addr ; Set address for access call read_EEPROM ; Get restore_flag, flag returns in W movwf temp ; Save flag for testing btfss temp,FIRST_TIME ; Have we been through startup before? goto get_band_freq ; No, load the default startup band and frequency ; Been through startup before, so restore movlw vfo_a_0 ; Pointer to VFO A frequency storage movwf FSR ; Points at vfo_a_0 frequency movlw restore_f_a ; Get pointer to saved start frequency movwf ee_addr ; Set the address for EEPROM read call read_EEPROM_freq ; Get the saved startup frequency clrf temp movlw restore_band_a ; Get band for VFO A movwf ee_addr call read_EEPROM ; Read VFO A band from EEPROM movwf vfo_a_band ; and set VFO A band movwf temp ; Save band number bcf STATUS,C ; Clear carry rlf temp,f ; Multiply band number by 4 rlf temp,f ; to calculate band index movf temp,w ; Get band index movwf band_index ; and set band index for VFO A IFDEF DUAL_VFO movlw vfo_b_0 ; Pointer to vfo b frequency movwf FSR ; Points at vfo_b_0 movlw restore_f_b ; Get pointer to saved start frequency movwf ee_addr ; Set the address for EEPROM read call read_EEPROM_freq ; Get the saved startup frequency movlw restore_band_b ; Get EEPROM startup band offset for VFO-B movwf ee_addr ; call read_EEPROM ; Get the startup band data into W movwf vfo_b_band ; Establish operating band for vfo b ENDIF call set_if_regs ; Set up IF offset, low band and high band regs goto frequency_set ; Band and frequency set, bypass getting default get_band_freq ENDIF ; ; Now get the user-programmed (or default) startup band and the ; the programmed startup frequency. ; movlw startup_band ; Get EEPROM startup band offset movwf ee_addr ; call read_EEPROM ; Get the startup band data into W movwf vfo_a_band ; Establish operating band for vfo a call get_band ; Establish the operating frequency IFDEF DUAL_VFO call copy_a_to_b ; Copy current freq and band to vfo b ENDIF IFDEF RESTORE_B_F ;??????????????????????????????????????????????????????????????????????????? ; ; The default startup band and frequency was used for startup. Now set flag ; so that subsequent start up uses saved band and frequency. bsf int_flags,FIRST_TIME ; Set first time flag bit in interrupt flags movf int_flags,w ; Get interrupt flags and save for next startup movlw restore_flag ; EEPROM address of restore flags movwf ee_addr ; Prime the EEPROM address call write_EEPROM ; Write flags to EEPROM ; Continue here after loading saved band and frequency. frequency_set bcf int_flags,POWER_UP ; Reset power up flag ENDIF call clear_lcd ; Clear the display IFNDEF LCD_16x2 movlw band_msg_offset ; Get the band message offset into W call display_message ; Send message to LCD. ENDIF call show_band ; Display the band number IFDEF LCD_16x2 call disp_vfo ; Display VFO in use ENDIF ; ; Display the power-on frequency. ; movlw default_decade ; Get the default decade increment movwf decade ; Store in decade clrw ; Prepare W for update call update_decade ; Init fstep3..0, cur_posn and display frequency ; ; Send power on-frequency to the DDS chip. ; call send_dds_word ; Send the power-on frequency to the ; DDS in serial mode ; call send_dds_word ; Send it twice. Sometimes needed for ; a clean start-up ; Not available for "Signal Generator" option IFDEF RESTORE_B_F ; Enable the interrupt if saving?????????????????????????????????? ; LCD is displaying start up frequency and the DDS chip is generating that ; frequency. Now set up Timer1 to generate an interrupt every 0.525 seconds. ; After number of interrupts, determined by count_of_ints, the band ; and frequency are saved in EEPROM. This band and frequency will be used ; on next power. ; movlw count_of_ints ; Get number of interrupts before saving movwf int_counter ; Initialize the counter bcf PIR1,TMR1IF ; Reset the Timer 1 interrupt flag clrf TMR1L ; Set Timer 1 Low and clrf TMR1H ; Timer 1 High to maximum count banksel PIE1 ; Switch to Bank 1 bsf PIE1,TMR1IE ; Enable Timer 1 overflow interrupt banksel INTCON ; Switch to Bank 0 movlw INTCON_INIT ; Get GIE and PEIE bits and enable movwf INTCON ; Global and Peripheral interrupts movlw T1CON_INIT ; Get T1CKPS1, T1CKPS0 and TMR1ON bits movwf T1CON ; and enable Timer 1 with prescale of 8 ENDIF ; ; 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 and the pushbuttons. 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,P ; * 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 bandswitch pushbutton (PIC-EL PB_2) is pressed while P ; * 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, interrupt or pushbutton ; activity (wait there!) ; Return here when encoder change detected btfss PB_flags,BAND_MODE ; Is band change mode active? goto step ; No, just step call change_band ; Yes, change_band goto main ;****************************************************************************** ; ; Function: step ; ; Purpose: Adjusts the frequency by examining the last_dir value and either ; adds or subtracts the value of fstep_3..0 from the current ; frequency setting. ; ; Rev 2.0: poll_encoder now determines the step size and stores the ; appropriate frequency increment in fstep_3..0. In this version, ; the accelerated adjustment to step size has been eliminated in ; favor of trying the PB_1 method of decade selection. 3-26-04 W3CD ; ;******************************************************************************* step ; Based on the knob direction, either add or ; subtract the increment, then update the ; LCD and DDS. btfsc last_dir,DIR_BIT ; Is the knob going up? goto up ; Yes, then add the increment down ; Else, shaft direction is CCW 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 IFDEF LCD_16x2 call disp_vfo ; Display VFO in use ENDIF call show_freq ; Display the new frequency on the LCD call send_dds_word ; Send the new control word to the DDS chip goto main ; Continue main loop ; ***************************************************************************** ; ; Function: change_band ; ; Purpose: This routine increments through the band table each time the ; knob moves a step, 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. ; ; Revisions: ; Rev 2.0: Modified for indexing into vfo_a_band and vfo_b_band ; registers. 4-7-04 W3CD ; ; ***************************************************************************** change_band movlw BAND_ADJUST ; Get offset to vfo_X_band addwf vfo_pointer,w ; Add to vfo_X_0 to point to vfo_X_band ; movwf FSR ; Store in file select register btfsc last_dir,DIR_BIT ; Are we going up in the band list? goto band_up ; If so, increment the band number ; Else, we are going down in the band list decf INDF,f ; Decrement the band number pointed to by FSR btfss INDF,MSB ; If bit 7 is set, then band number is negative goto band_ok ; If still positive, then band number is OK ; Else wrap around to top movlw num_bands - 1 ; Get the top band number movwf INDF ; Save it in band pointed to by FSR goto band_ok ; Finish up band_up ; Arrive here when incrementing band number incf INDF,f ; Do it movlw num_bands ; Load w with max band number + 1 subwf INDF,w ; Test if over the limit btfss STATUS,C ; If carry is set, then we're over goto band_ok ; Else, the band number is within range clrf INDF ; If over, then reset the band number band_ok ; Arrive here with a valid band number call clear_lcd ; Clear the display IFNDEF LCD_16x2 movlw band_msg_offset ; Get the band message offset into W call display_message ; Send message to LCD. ENDIF call show_band ; Display the band number ; Now display the frequency IFDEF LCD_16x2 call disp_vfo ; Display VFO in use ENDIF call get_band ; Read the band frequency from EEPROM movlw default_decade ; When changing bands reset curser movwf decade ; to default decade clrw call update_decade ; Update fstep0..3, cur_posn, and display frequency call send_dds_word ; Send the control word to the DDS chip return ; Back to the caller ;***************************************************************************** ; ; Function: get_band ; ; Purpose: This routine reads the frequency value of a band table entry ; from EEPROM and stores it in the active VFO registers. ; If Conversion Oscillator option: IF Offset, band low and band ; high values are set in the registers. ; ; Input: vfo_pointer ; ; Output: The band frequency in the current vfo frequency registers ; ; Revisions: ; Rev 2.0: The frequency is now read out of EEPROM and stored in ; either the VFO A or VFO B frequency registers. - W3CD ; ;***************************************************************************** get_band IFDEF RESTORE_B_F ;??????????????????????????????????????????????????????????????????????????? btfss int_flags,POWER_UP ; Is processing for band change or power up goto get_freq ; Band change, get the frequency for new band btfsc int_flags,FIRST_TIME ; Power up, is this the first time thru startup goto byp_load_freq ; No, band and frequency has already been setup get_freq ; First time through startup or band change ENDIF clrf temp ; Clear the temp variable movlw BAND_ADJUST ; Offset to vfo_X_band addwf vfo_pointer,w ; Calc address of vfo_X_band movwf FSR ; Prepare for indirect addressing movf INDF,w ; Get the band number in use movwf temp ; Save band in temp bcf STATUS,C ; Make sure Carry bit is clear rlf temp,f ; Shift left twice to get 4*band number to rlf temp,f ; obtain the proper band_table index movf temp,w ; Get the band_table index into W movwf band_index ; Save band index for table access addlw band_table ; Add starting offset of EEPROM band table movwf ee_addr ; Prepare EEPROM address ; incf FSR,f ; Now point to vfo_X_0 call read_EEPROM_freq ; Get the frequency byp_load_freq ; Bypass setting up band and frequency IFDEF CVFO set_if_regs ; <****** Entry Point ****** ; Using band_index, obtain the if_table entry and ; copy IF offset from table and store in ioff_3..0 ; clrf temp ; Clear the temp variable movlw FREQ_COUNT ; There are four bytes to copy movwf count ; Set up count variable movlw ioff_0 ; Get target address movwf FSR ; Store in pointer register movf band_index,w movwf temp get_if_loop movf temp,w ; Get the if_table index into W call if_table ; Get a if_table byte movwf INDF ; Store it into ioff_3..0 incf FSR,f ; Point to next higher offset byte, ioff_0..3 incf temp,f ; Increment table index decfsz count,f ; Decrement loop count goto get_if_loop ; loop while more bytes remain IFDEF BAND_LIMITS ; Initialize the lower frequency band limits movlw FREQ_COUNT ; There are four bytes to copy movwf count ; Set up count variable movlw band_low_0 ; Get target address - low limit movwf FSR ; Store in pointer register movf band_index,w ; Get index into table movwf temp ; Save the table index get_lim_low movf temp,w ; Get the band index into W call low_table ; Get a low_table byte movwf INDF ; Store it into band_low_0..3 incf FSR,f ; Point to next higher offset byte, band_low_0..3 incf temp,f ; Increment table index decfsz count,f ; Decrement loop count goto get_lim_low ; loop while more bytes remain ; Initialize the upper freqquency band limits movlw FREQ_COUNT ; There are four bytes to copy movwf count ; Set up count variable movlw band_high_0 ; Get target address - high limit movwf FSR ; Store in pointer register movf band_index,w ; Get index into table movwf temp ; Save the table index get_lim_high movf temp,w ; Get the band index into W call high_table ; Get a high_table byte movwf INDF ; Store it into band_high_0..3 incf FSR,f ; Point to next higher byte, band_high_0..3 incf temp,f ; Increment table index decfsz count,f ; Decrement loop count goto get_lim_high ; loop while more bytes remain ENDIF ENDIF return ; Done! ; ***************************************************************************** ; ; Function: add_step ; ; Purpose: This routine adds the 32 bit value of fstep to the 32 bit ; value in vfo_X_x. 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 vfo_X_x ; ; Output: The sum of fstep and vfo_X_x is stored in vfo_X_x. When ; incrementing this value may exceed the maximum. When ; decrementing, it may go negative. ; ; Revisions: ; Rev 2.0 Modified for use with dual VFOs. 4-7-04 W3CD ; ; ***************************************************************************** add_step clrf temp ; Clear the temp variable ; movf vfo_pointer,w ; Get the pointer to vfo_X_0 movwf FSR ; Put this into FSR for indirect addressing movf fstep_0,w ; Get low byte of the increment addwf INDF,f ; Add it to the low byte of freq ; There may be a carry out of this add... movlw 3 ; Propagate ripple carries over next 3 regs movwf count ; call ripple_carry ; This function handles it decf FSR,f ; Adjust FSR to point to decf FSR,f ; vfo_X_1 ; movf fstep_1,w ; Get the next increment byte addwf INDF,f ; Add it to the next higher byte movlw 2 ; Propagate any ripple carries into movwf count ; the next two registers call ripple_carry ; decf FSR,f ; Point to vfo_X_2 ; movf fstep_2,w ; Get the next to most significant increment addwf INDF,f ; Add it to the freq byte incf FSR,f ; Point to vfo_X_3 btfss STATUS,C ; Any carry? goto add_step1 ; No, add last byte incf INDF,f ; Ripple carry up to the highest byte add_step1 movf fstep_3,w ; Get the most significant increment byte addwf INDF,f ; Add it to the most significant freq return ; Return to the caller ; ***************************************************************************** ; ; Function: ripple_carry ; ; Purpose: This function propagates carries that result from addition ; into the higher order bytes of the frequency registers ; ; Input: FSR points to the register which was just added, ; temp is cleared by the caller ; count is set to the number of higher order bytes to process ; ; Output: Any carry resulting from an addition is propagated into the ; number of higher order bytes specified by count ; ; Revisions: ; Rev 2.0 Created for use with dual VFOs. 4-7-04 W3CD ; ; ***************************************************************************** ripple_carry rlf temp,w ; Put carry from previous add into temp LSB incf FSR,f ; Point to next freq reg. addwf INDF,f ; Ripple any carry into next freq reg decfsz count,f ; Decrement loop count goto ripple_carry ; Loop until all registers are accounted for return ; Back to caller ; ; ***************************************************************************** ; ; Function: check_add ; ; Purpose: Check if freq exceeds the upper limit. ; ; Input: vfo_select, vfo_a and vfo_b registers ; ; Output: If freq is below the limit, it is unchanged. Otherwise, it ; is set to equal the upper limit. ; ; Revisions: ; Rev 2.0: Modified to use dual VFOs. 4-7-04 W3CD ; ; ***************************************************************************** check_add ; First, get the address of vfo_X_3 movlw TABLE_ADJUST ; Offset to 4th element addwf vfo_pointer,w ; Add in the pointer to vfo_X_0 movwf FSR ; Put this into FSR for indirect addressing bcf STATUS,C ; ; Check the most significant byte. ; IFDEF BAND_LIMITS movf band_high_3,w ; Get high band MSByte ELSE movlw limit_3 ; Get high limit value ENDIF subwf INDF,w ; Subtract the limit value btfss STATUS,C ; Are we at the limit for the byte? goto check_exit ; No, below. Checks are done. btfss STATUS,Z ; Are the bytes equal? goto set_max ; No, so vfo_X_3 > limit_3. ; ; Check the second most significant byte when MSB equals limit_3 ; decf FSR,f ; Point to 2nd MSB IFDEF BAND_LIMITS movf band_high_2,w ; Get high band 2nd MSByte ELSE movlw limit_2 ; Get high limit 2nd value ENDIF subwf INDF,w ; Subtract limit value btfss STATUS,C ; Are we at the limit for the byte? goto check_exit ; No, below. Checks are done. btfss STATUS,Z ; Might they be equal? goto set_max ; Nope, so vfo_X_2 > limit_2 ; ; Check the third most significant byte. ; decf FSR,f ; Point to 3rd MSB IFDEF BAND_LIMITS movf band_high_1,w ; Get high band 3rd MSByte ELSE movlw limit_1 ; Get high limit 3rd value ENDIF subwf INDF,w ; Subtract limit value btfss STATUS,C ; Are we at the limit for the byte? goto check_exit ; No, below. Checks are done. btfss STATUS,Z ; Check if the bytes are equal goto set_max ; No, so vfo_X_1 > limit_1 ; ; Check the least significant byte. ; decf FSR,f ; Point to LSB IFDEF BAND_LIMITS movf band_high_0,w ; Get high band LSByte ELSE movlw limit_0 ; Get high limit LSByte ENDIF subwf INDF,w ; Subtract limit value btfss STATUS,C ; Are we at the limit for the byte? goto check_exit ; No, below. Checks are done. ; Else, it's freq is over limit set_max movf vfo_pointer,w ; Get pointer to vfo_X_0 movwf FSR ; into indirect addressing reg. IFDEF BAND_LIMITS movf band_high_0,w ; Get low byte of limit ELSE movlw limit_0 ENDIF movwf INDF ; Store in vfo_X_0 incf FSR,f ; Point to vfo_X_1 IFDEF BAND_LIMITS movf band_high_1,w ; Get next limit byte ELSE movlw limit_1 ENDIF movwf INDF ; Store it in vfo_X_1 incf FSR,f ; Point to vfo_X_2 IFDEF BAND_LIMITS movf band_high_2,w ; Get next byte of limit ELSE movlw limit_2 ENDIF movwf INDF ; Store it in vfo_X_2 incf FSR,f ; Point to vfo_X_3 IFDEF BAND_LIMITS movf band_high_3,w ; Get the high byte of limit ELSE movlw limit_3 ENDIF movwf INDF ; Store it in vfo_X_3 check_exit return ; Return to the caller ; ***************************************************************************** ; ; Function: sub_step ; ; Purpose: Subtract the increment step from freq, checking that it does ; not go below zero. ; ; Input: The values in fstep and vfo_X_x. ; ; Output: The updated value in vfo_X_x. ; ; Revisions: ; Modified to use dual VFOs. 4-7-04 W3CD ; ; ***************************************************************************** sub_step call invert_fstep ; Invert fstep_3..0 to perform the subtraction call add_step ; Add the complement to do the subtraction ; Determine which VFO is active movlw TABLE_ADJUST ; Get offset to 4th element addwf vfo_pointer,w ; Get address of vfo_X_3 movwf FSR ; Put this into FSR for indirect address IFDEF BAND_LIMITS goto low_limit_chk ; Check if frequency above lower limit ELSE btfss INDF,MSB ; Is high order frequency byte "negative"? goto sub_step_exit ; No, keep going ; Else, set it to zero. set_min clrf INDF ; Clear vfo_X_3 decf FSR,f ; Next byte clrf INDF ; Clear vfo_X_2 decf FSR,f ; Next byte clrf INDF ; Clear vfo_X_1 decf FSR,f ; Last byte clrf INDF ; Clear vfo_X_0 sub_step_exit call invert_fstep ; Restore the original fstep value return ; Return to the caller ENDIF ;***************************************************************************** ; ; Function: invert_fstep ; ; Purpose: Support function for sub_step and calibrate. This function ; negates the value of fstep_3..0 to assist in the frequency ; decrement. This operation is performed twice by sub_step, and ; is also used by calibrate. ; ; Input: fstep_3, fstep_2, fstep_1, fstep_0 ; ; Output: fstep_3..0 contain the 2's complement of their original value ; ; Revisions: ; Made an indepndent function. 4-7-04 W3CD ; ; ***************************************************************************** invert_fstep ; Invert the bits in comf fstep_0,f ; fstep_0 comf fstep_1,f ; fstep_1 comf fstep_2,f ; fstep_2 comf fstep_3,f ; fstep_3 incfsz fstep_0,f ; Now incremnt fstep_0 to get 2's complement goto invert_done ; If fstep_0 > 0, then done ; Else, there was a carry out of fstep_0 incfsz fstep_1,f ; Add 1 to fstep_1 goto invert_done ; If fstep_1 > 0, then done ; Else, there was a carry out of fstep_1 incfsz fstep_2,f ; Add 1 to fstep_2 goto invert_done ; if fstep_2 > 0, then done ; Else, there was a carry out of fstep_2 incf fstep_3,f ; Increment the high byte invert_done return ; Back to caller ; IFDEF BAND_LIMITS ; **************************************************************************** ; ; Function: low_limit_chk ; ; Purpose: Conversion Oscillator option. - Test the new frequency to ; see if it is above the lower band limit. If not, the frequency ; is set to the band lower limit. ; ; Input: vfo_x_0..3, band_low_0..3, FSR pointing to vfo_x_3 ; ; Output: Original frequency if above low limit or band_low_0..3 in ; vfo_X_0..3 ; ; **************************************************************************** low_limit_chk ; ; Check the most significant byte. btfsc INDF,MSB ; Is vfo_X_3 negative? goto set_low ; Yes, set to lower frequency limit movf INDF,w ; Get vfo_X_3 subwf band_low_3,w ; Subtract the limit value btfss STATUS,C ; Are we at the limit for the byte? goto limit_exit ; No, above. btfss STATUS,Z ; Are the bytes equal? goto set_low ; No, so vfo_X_3 > limit_3. ; ; Check the second most significant byte when MSB equals limit_3 ; decf FSR,f ; Point to 2nd MSB movf INDF,w ; Get vfo_X_2 subwf band_low_2,w ; Subtract limit value btfss STATUS,C ; Are we at the limit for the byte? goto limit_exit ; No, above. Check next. btfss STATUS,Z ; Might they be equal? goto set_low ; Nope, so vfo_X_2 > limit_2 ; ; Check the third most significant byte. ; decf FSR,f ; Point to 3rd MSB movf INDF,w ; Get vfo_X_1 subwf band_low_1,w ; Subtract limit value btfss STATUS,C ; Are we at the limit for the byte? goto limit_exit ; No, above. Checks are done. btfss STATUS,Z ; Check if the bytes are equal goto set_low ; No, so vfo_X_1 > limit_1 ; ; Check the least significant byte. ; decf FSR,f ; Point to LSB movf INDF,w ; Get vfo_X_0 subwf band_low_0,w ; Subtract limit value btfss STATUS,C ; Are we at the limit for the byte? goto limit_exit ; No, above. Checks are done. ; The frequency is below the band lower limit. Set frequency to the ; band starting frequency. set_low movf vfo_pointer,w ; Get pointer to vfo_X_0 movwf FSR ; into indirect addressing reg. movf band_low_0,w movwf INDF ; Store in vfo_X_0 incf FSR,f ; Point to vfo_X_1 movf band_low_1,w movwf INDF ; Store it in vfo_X_1 incf FSR,f ; Point to vfo_X_2 movf band_low_2,w movwf INDF ; Store it in vfo_X_2 incf FSR,f ; Point to vfo_X_3 movf band_low_3,w movwf INDF ; Store it in vfo_X_3 limit_exit call invert_fstep ; Put fstep back to original value return ; Return to caller ENDIF ; ;***************************************************************************** ; ; Function: poll_encoder ; ; Purpose: This routine does the following: ; 1. Reads the encoder bits until a change is detected, then ; determines the direction the knob was moved. ; 2. Performs PIC-EL mechanical shaft encoder debounce and ; detent processing is so enabled. ; 3. Reads PB_2 and determines if a band change is ; requested. ; ; Input: Knob input read from port A ; PB_2 ; ren_old -> the last encoder bits read ; last_dir -> the last direction moved ; ; Output: ren_read: The contents of PORTA when the encoder was moved ; ren_new: The current encoder bits ; last_dir: The last direction (0 = down, 2 = up [AKA UP_BIT]) ; ; Revisions: ; ; Ver 2.0: Modified to accomodate detented PIC-EL encoder., et. al. ; 3-31-04 W3CD ; ;***************************************************************************** ; poll_encoder IFDEF RESTORE_B_F ;??????????????????????????????????????????????????????????????????????????? btfsc int_flags,T1_INT ; Has a Timer 1 interrupt occurred goto process_int ; Yes, go process it proc_int_rtn ; Return from interrupt processing ENDIF call process_pb ; Check if there is pushbutton activity movf PORTA,w ; Get the current encoder value movwf ren_read ; Save it IFDEF DETENT_ENCODER call wait_1ms ; debounce time movf PORTA,w ; read the port again xorwf ren_read,w ; Compare with previous value btfss STATUS,Z ; Are they equal? goto poll_encoder ; Poll again if not ENDIF movlw ENCODER_BITS ; 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,Z ; Check zero-flag (zero if no change) goto poll_encoder ; No change, keep looking until it changes ; Else, Zero-flag is not set, so continue on ; It changed. Now determine which direction the encoder turned. ;=============================================================================P ; Encoder bits are on RA0 and RA1 - the two low order bits of ren_new P ; A and B are "gray code" - 90 degrees out of phase (quadrature) P ; ___ ___ P ; | | | | P ; A ____| |___| |___ P ; ___ ___ P ; | | | | P ; B ___| |___| |___ P ; ^ ^ ^ ^ ^ ^ ^ ^ P ; a b c d a b c d P ; P ; A B P ; At point a: 0 0 P ; At point b: 1 0 P ; At point c: 1 1 P ; At point d: 0 1 P ; P ; Going UP, the sequence is a,b,c,d,a,b,c,d, etc. so the sequence is: P ; 00, 10, 11, 01, 00, 10, 11, 01, etc. P ; P ; Going DOWN, the sequence is d,c,b,a,d,c,b,a, etc. so the sequence is: P ; 01, 11, 10, 00, 01, 11, 10, 00, etc. P ; P ; To determine if the sequence is UP or DOWN: P ; 1) Take the "Right-Bit" of any pair. P ; 2) XOR it with the "Left-Bit" of the next pair in the sequence. P ; 3) If the result is 1 it is UP P ; If the result is 0 it is DOWN P ; P ; The direction flag is 0 (DOWN) or 2 (UP) because of bit positioning P ;=============================================================================P bcf STATUS,C ; Clear the carry bit to prepare for rotate P rlf ren_old,f ; Rotate old bits left to align "Right-Bit" P movf ren_new,w ; Set up new bits in W P xorwf ren_old,f ; XOR old (left shifted) with new bits P movf ren_old,w ; Put XOR results into W also P andlw UP_BIT ; Mask to look at only "Left-Bit" of pair P movwf next_dir ; Save result (in W) as direction (bit=UP) P xorwf last_dir,w ; See if direction is same as before P ; ; Prevent encoder slip from giving a false change in direction. ; IFNDEF DETENT_ENCODER btfsc STATUS,Z ; Zero flag set? (i.e, is direction same?) P goto pe_continue ; Yes, same direction so no slip; keep goingP movf next_dir,w ; No Zero-flag, so direction changed P movwf last_dir ; Update the direction indicator P movf ren_new,w ; Save the current encoder bits (now in W) P movwf ren_old ; for next time P goto poll_encoder ; Try again ENDIF pe_continue clrf last_dir ; Clear last_dir (default is DN) P btfsc ren_old,DIR_BIT ; Are we going UP? P goto pe_up ; Yes, go process it. ; Else, we are goiong down IFDEF DETENT_ENCODER movf ren_new,w ; Get the current encoder bits movwf ren_old ; Save them in ren_old for the next time decf enc_counter,f ; decrement the inter-detent counter btfsc enc_counter,0 ; Check if bit 0 is cleared goto poll_encoder ; If not, then poll some more btfsc enc_counter,1 ; Else, check if bit 1 is cleared goto poll_encoder ; If not, then poll some more ; Else, assume the encoder is on a detent ENDIF goto pe_movement ; Indicate that the encoder has moved pe_up movlw UP_BIT ; Get UP value movwf last_dir ; and set in last_dir IFDEF DETENT_ENCODER movf ren_new,w ; Get the current encoder bits movwf ren_old ; Save them in ren_old for the next time incf enc_counter,f ; Increment the encoder counter btfsc enc_counter,0 ; Return only on modulo 4 counts goto poll_encoder ; btfsc enc_counter,1 goto poll_encoder ; Loop again if not modulo 4 ENDIF pe_movement ; Arrive here when encoder is being turned movf ren_new,w ; Get the current encoder bits movwf ren_old ; Save them in ren_old for the next time ; btfsc ren_read,PB_2 ; Check if PB_2 is pressed goto pe_no_band ; Skip if it's not pressed. ; Else, we're in Band Mode bsf PB_flags,BAND_MODE ; Set the appropriate flag bit to ; prevent normal PB_2 processing. goto pe_exit ; Finish up pe_no_band bcf PB_flags,BAND_MODE ; Clear the band mode flag pe_exit return ; Return to the caller ;**************************************************************************** ; ; Function: process_pb ; ; Purpose: This function examines the state of PB_1, PB_2 and PB_3 ; and has mulitple operating characteristics. ; ; 1. Pressing and releasing either button individually changes ; the cursor position on the display and adjusts the frequency ; step to another decade. The cursor and frequency step wrap ; around the 10MHz position, returning the frequency increment ; back to 1Hz. ; ; 2. If either PB1 or PB_2 (but not both) is held down for more ; than the specified delay, (1 second) auto repeat is enabled ; where the decade and cursor position are updated continuously ; until the button is released. The update rate is 4 times/sec. ; ; 3. If both PB_1 and PB_2 are held down for more than 2 seconds, ; then the EEPROM contents are updated. The data written ; depends on whether the user is changing bands or changing ; the frequency. ; ; 4. If PB_3 is pressed then a VFO action is performed, depending ; on the state of PB_1. ; ; Inputs: PORTA bits PB_1, PB_2, PB_3 and PB_flags ; ; Outputs: If a button is pressed, then various control variables are ; updated. The function exits if no buttons are pressed. ; ; Revisions: ; ; Ver 2.0: Updated 3-27-04 by W3CD. All of the aforementioned ; features were added while the PICELgen Rev 1.4 ; features were removed. ; ;**************************************************************************** process_pb btfsc PB_flags,PCAL_FLG ; Check if in calibrate mode goto process_pb_exit ; If bit is set, skip processing ; Else, examine the button status IFDEF DUAL_VFO ; If Dual VFO mode is enabled... btfss PORTA,PB_3 ; Check for VFO change activity goto process_pb_3 ; If so, then handle vfo request ENDIF btfsc PORTA,PB_1 ; Is PB_1 being pressed? goto pb_2_check ; Check PB_2 if not. ; Else, there's further processing to do btfss PORTA,PB_2 ; Check PB_2 to see if both are pressed goto process_both ; If it's active, process both process_pb_1 ; Arrive here to process just PB_1 movlw PB_RPT_WAIT ; Setup the repeat delay timer movwf PB_wait_count ; bcf PB_flags,PB_RPT_FLG ; Clear repeat flag pb_1_release_wait call wait_32ms ; Wait a bit before checking again btfsc PORTA,PB_1 ; Is PB_1 still behing held? goto pb_1_released ; If it's released, adjust decade and exit btfss PORTA,PB_2 ; Else, check for a late pressing of PB_2 goto process_both ; If PB_2 was pressed, then process both IFDEF DUAL_VFO ; If Dual VFO mode is enabled... btfss PORTA,PB_3 ; Check for VFO change activity goto process_pb_3 ; If so, then handle vfo request ENDIF ; Else, test if repeat delay has expired decf PB_wait_count,f ; Decrement wait period btfss STATUS,Z ; Check the Zero bit goto pb_1_release_wait ; Keep looping until something happens ; Else, repeat delay has expired. movlw DECADE_INCREMENT ; Increase decade adjustment call update_decade ; Bump the decade-related variables movlw PB_RPT_DLY ; Load the repeat interval value movwf PB_wait_count ; into PB_wait_count bsf PB_flags,PB_RPT_FLG ; update when PB_1 is released goto pb_1_release_wait ; Loop some more pb_1_released movlw DECADE_INCREMENT ; Increase decade adjustment goto process_pb_release ; common code for pushbuttons ;****************************************************************************** ; ; Arrive here when PB_1 was not pressed. ; Check for PB_2 activity. ; ;****************************************************************************** pb_2_check btfss PB_flags,BAND_MODE ; Check if poll_encoder detected band change goto pb_2_normal ; If not, then do normal processing ; Else, in Band Mode btfss PORTA,PB_2 ; Is PB_2 still being pressed? goto process_pb_exit ; If so, then exit ; Else, it was released. Do housekeeping. clrf PB_flags ; Clear the PB flags goto process_pb_exit ; Skip other processing. pb_2_normal ; Regular handling of PB_2 btfsc PORTA,PB_2 ; Is PB_2 being pressed? goto process_pb_exit ; Bail out if not. ; Else, process PB_2 pressing btfsc PB_flags,PB_2_DLY ; If already in a repeat delay, goto pb_2_release_wait ; Then skip the preliminaries ; Else, this is the first time through movlw PB_RPT_WAIT ; Setup the repeat delay timer movwf PB_wait_count ; bcf PB_flags,PB_RPT_FLG ; Clear repeat flag bsf PB_flags,PB_2_DLY ; Set the PB2 count down flag pb_2_release_wait ; Wait for operator to do something call wait_32ms ; Linger a while before checking again btfsc PORTA,PB_2 ; Is PB_2 still behing held? goto pb_2_released ; If it's released, adjust decade and exit ; btfss PORTA,PB_1 ; Else, check for a late pressing of PB_1 goto process_both ; If PB_1 was pressed, then process both ; Else, test if repeat delay has expired decf PB_wait_count,f ; Decrement wait period btfsc STATUS,Z ; Check the Zero bit goto pb_2_timeout ; If it's set, then timeout occurred and ; it's time to adjust the decade ; Else, test if we're still waiting for btfsc PB_flags,PB_2_DLY ; initial timeout before repeating goto process_pb_exit ; Exit if this is the case. goto pb_2_release_wait ; Else, we're in a repeat loop pb_2_timeout ; Arrive here when a delay has expired. bcf PB_flags,PB_2_DLY ; Clear the delay flag movlw DECADE_DECREMENT ; Decrease decade adjustment call update_decade ; Bump the decade-related variables movlw PB_RPT_DLY ; Load the repeat interval value movwf PB_wait_count ; into PB_wait_count bsf PB_flags,PB_RPT_FLG ; update when PB_2 is released goto pb_2_release_wait ; Loop some more pb_2_released movlw DECADE_DECREMENT ; Load up decade adjustment ; And continue with common PB code ;************************************************************************ ; ; Common code to handle the release of either PB_1 or PB_2 in the ; decade adjust mode. Wreg contains the cursor adjustment value. ; ;************************************************************************ process_pb_release btfsc PB_flags,PB_RPT_FLG ; If repeat flag was set, goto pb_cf_exit ; don't do the final adjustment. call update_decade ; Else, adjust decade of interest goto pb_cf_exit ; Clear the flags & QRT this function ;************************************************************************ ; ; Arrive here when both PB_1 and PB_2 were pressed. There are two ; ways this funcion operates, depending on the the state of the ; band_mode bit in PB_flags. If this bit is cleared, then PB2 was ; pressed and the shaft encoder was not turned. If PB2 was pressed and ; the shaft encoder was rotated before the PB2 repeat timeout expired, ; then the user is accessing the band memories in EEPROM and, as a ; result, the band_mode bit is set. This bit stays set until PB2 is ; released. This code section exploits this operation. ; ; In band frequency update mode, the code updates the current band's ; frequency in EEPROM and then displays which band was updated before ; reverting back to the normal frequency display. ; ; If PB1 is pressed and held while in band change mode, the default ; startup band location in EEPROM is written with the current band. ; A message is displayed to alert the user of this fact. ; ;************************************************************************ process_both movlw PB_INIT_FREQ_WAIT ; Setup the repeat delay timer movwf PB_wait_count ; both_release_wait ; Wait for the PBs to be released call wait_32ms ; Use our favorite delay btfsc PORTA,PB_1 ; If PB_1 was released, goto process_pb_exit ; Then exit before new freq set btfsc PORTA,PB_2 ; Check PB_2 for same condition goto process_pb_exit ; Quit if operator released it ; Else, both buttons are still held decf PB_wait_count,f ; Update hold timer btfss STATUS,Z ; Check the Zero bit goto both_release_wait ; Loop if there's still more time ; Else, 2 seconds are up. ; The user desired an EEPROM update ; Determine what will be updated. btfss PB_flags,BAND_MODE ; Check if it's default startup band goto update_band_freq ; Nope, it's the current band freq. ; Else, it is the power-up band call update_EEPROM_band ; Write the new startup band ; call clear_lcd ; Clear the display movlw upd_msg_offset ; Display the update call display_message ; message call wait_512ms ; Show this for 1/2 sec call clear_lcd ; Clear the display movlw init_msg_offset ; Point to new startup band msg call display_message ; Display it on LCD call wait_a_sec ; for 1 second goto pb_update_exit ; Done! update_band_freq ; Arrive here to update freq. call update_EEPROM_freq ; update the current band frequency call clear_lcd ; Now display a message on LCD ; to inform operator of update ; so he releases the buttons! movlw upd_msg_offset ; Point to the message call display_message ; Display it pb_update_exit IFDEF LCD_16x2 call show_band ; Display band being modified call disp_vfo ; Display VFO in use ENDIF call show_freq ; revert back to usual display pb_cf_exit ; Arrive here to clear flags and exit clrf PB_flags ; clear all of the PB flags process_pb_exit return ; Back to caller ;****************************************************************************** ; ; Arrive here when PB_3 was pressed, indicating some sort of vfo action ; ;****************************************************************************** IFDEF DUAL_VFO process_pb_3 call wait_32ms ; Use our favorite delay btfss PORTA,PB_1 ; If PB_1 was not pressed, then skip bsf PB_flags,VFO_ACTION ; Else, set flag that VFO A->B copy ; was requested btfss PORTA,PB_3 ; If PB_3 was released, then process goto process_pb_3 ; Loop until operator releases PB_3 ; Arrive here after PB_3 was released. btfss PB_flags,VFO_ACTION ; If no vfo copy, then skip goto swap_vfo ; Else, swap VFOs ; Copy VFO A to VFO B, regardless of call copy_a_to_b ; which one is active at the time. goto process_pb3_exit ; All done. swap_vfo btfss vfo_select,VFO_SEL ; Determine which vfo was active goto vfo_a ; If VFO A then skip ; Else, VFO B was active bcf vfo_select,VFO_SEL ; Select VFO A movlw vfo_a_0 ; Get address of vfo_A_0 movwf vfo_pointer ; Set up vfo pointer register goto process_pb3_exit ; Use the common exit vfo_a ; Change VFO setting to B bsf vfo_select,VFO_SEL ; Select VFO B movlw vfo_b_0 ; Get address of vfo_b_0 movwf vfo_pointer ; Set up vfo pointer register process_pb3_exit ; Finish up PB_3 handling clrf PB_flags ; Clear all PB flags IFDEF CVFO ; VFO switched, make sure band_index is correct clrf temp ; Clear the temp variable movlw BAND_ADJUST ; Offset to vfo_X_band addwf vfo_pointer,w ; Calc address of vfo_X_band movwf FSR ; Prepare for indirect addressing movf INDF,w ; Get the band number in use movwf temp ; Save band in temp bcf STATUS,C ; Make sure Carry bit is clear rlf temp,f ; Shift left twice to get 4*band number to rlf temp,f ; obtain the proper band_table index movf temp,w ; Get the band_table index into W movwf band_index ; Save band index for table access call set_if_regs ; Init IF offset, low band and high band ENDIF IFDEF LCD_16x2 call disp_vfo ; Display VFO in use call show_band ; Display band in use ENDIF movlw default_decade ; When changing VFO's reset curser movwf decade ; to default decade clrw call update_decade ; Display frequency with default cursor position call send_dds_word ; Send the control word to the DDS chip goto process_pb_exit ; Use the common exit ;**************************************************************************** ; ; Function: copy_a_to_b ; ; Purpose: This function copies the contents of the VFO A registers ; into the VFO B registers ; ; Inputs: vfo_a_band, vfo_a_0, vfo_a_1, vfo_a_2, vfo_a_3 ; ; Outputs: vfo_b_band, vfo_b_0, vfo_b_1, vfo_b_2, vfo_b_3 ; ; Revisions: ; Ver 2.0: Created 4-7-04 by W3CD. ; ;**************************************************************************** copy_a_to_b movf vfo_a_0,w ; Copy all 4 bytes of VFO A movwf vfo_b_0 ; into VFO B movf vfo_a_1,w movwf vfo_b_1 movf vfo_a_2,w movwf vfo_b_2 movf vfo_a_3,w movwf vfo_b_3 movf vfo_a_band,w ; Also copy VFO A band information movwf vfo_b_band ; into VFO B return ENDIF ;**************************************************************************** ; ; Function: update_decade ; ; Purpose: Support function for PB_1_check. Adjusts the cursor position ; and decade table pointer. It also calls get_decade to update ; the values of fstep_3 through fstep_0. Lastly, it calls ; show_freq to update the cursor position on the LCD. ; ; Inputs: W contains the decade adjustment. ; ; Outputs: cur_pos is modified, modulo 8 (0 to 7) ; decade is modified ; fsetp_3..0 are modified in the sequence of: ; 1Hz, 10Hz, 100Hz, 1KHz, 10KHz, 100KHz, 1MHz, 10 MHz, 1Hz, etc., ; depending on direction. ; ; Revisions: ; Ver 2.0: Created 3-27-04 by W3CD. ; ;**************************************************************************** update_decade addwf decade,w ; Add the decade adjustment andlw DECADE_MASK ; Only using 8 decades here and the ; decades wrap around the 10MHz position movwf decade ; Store updated decade call cursor_table ; Get the translated cursor position movwf cur_pos ; Save the updated cursor value. call get_decade ; Update fstep_3..0 call show_freq ; update display to show new cursor position return ; Return to the caller ;**************************************************************************** ; ; Function: get_decade ; ; Purpose: Support function which sets new increment values for ; fstep_3 through fstep_0 using values in decade_table ; ; Inputs: decade ; ; Outputs: fstep_3, fstep_2, fstep_1 and fstep_0 are updated ; ; Revisions: ; Ver 2.0: Added 3-27-04 by W3CD. ; ;**************************************************************************** get_decade movlw FREQ_COUNT ; There are four bytes to copy movwf count ; Set up count variable movlw fstep_3 ; Get target address movwf FSR ; Store in pointer register movf decade,w ; Get the decade index movwf temp ; Copy it to temp variable bcf STATUS,C ; Make sure Carry bit is clear rlf temp,f ; Shift decade index 2 place to left rlf temp,f ; to develop table index get_decade_loop movf temp,w ; Get the decade pointer into W call decade_table ; Get a decade byte movwf INDF ; Store it decf FSR,f ; Point to next lowest fstep byte incf temp,f ; Increment decade table pointer decfsz count,f ; Decrement loop count goto get_decade_loop ; loop while more bytes remain ; return ; Exit when done ; ; ***************************************************************************** ; ; Function: calibrate ; ; 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 ; If a 16-character LCD is used," 10,000.000 CAL " is displayed ; ; 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 ; ; Revisions: ; Rev 2.0: Deleted EEPROM osc byte reads since the init code ; was changed to read them; this saves program memory ; space. Modified for use with dual vfos. 4-8-04 W3CD ; ; ***************************************************************************** calibrate bsf PB_flags,PCAL_FLG ; Set the calibrate bit to prevent cursor movement movlw cal_freq_0 ; Set frequency to 10MHz by setting freq movwf vfo_a_0 ; to the binary equivalent of 10 MHz movlw cal_freq_1 ; movwf vfo_a_1 ; movlw cal_freq_2 ; movwf vfo_a_2 ; movlw cal_freq_3 ; movwf vfo_a_3 ; ; ; The starting reference oscillator values have already been read ; from EEPROM. Convert reference freq and display it. ; call show_freq ; Display the frequency on the LCD IF LCDCHAR == 16 IF LCD16_LINEAR == 0 movlw PICEL_LONG_9 + 4 ; Point LCD at position 13 for AmQRP 16x1 LCD ELSE movlw CMD_MOV_CUR_DISP+12 ; Short jump to LCD position 13 ENDIF call cmnd2LCD ; Position the cursor movlw cal_offset ; Display "CAL" on LCD call display_message ; (position 16 is blank) ENDIF call send_dds_word ; If entering Calibrate from power up, DDS may ; not be initialized properly. So, update twice. cal_loop call send_dds_word ; Update the DDS chip call poll_encoder ; Wait until the encoder has moved. btfsc PORTA,PB_1 ; Check if PB1 was pressed goto cal_done ; If released, then exit calibrate mode ; Else, stay in cal and update osc. clrf fstep_3 ; Clear the three most significant clrf fstep_2 ; bytes of fstep clrf fstep_1 ; movlw SMALL_FSTEP ; Assume that we are adjusting slowly movwf fstep_0 ; Use small increment btfsc ren_read,PB_3 ; Was the encoder changing slowly? goto update_osc ; Yes, then continue with small increment movlw LARGE_FSTEP ; No, then use the large increment movwf fstep_0 ; update_osc nop ; Wait a cycle btfsc last_dir,DIR_BIT ; Are we moving down? goto faster ; No, increase the osc value ; ; slower ; call invert_fstep ; Use this handy function to complement fstep faster movf fstep_0,w ; Get the low byte increment addwf osc_0,f ; Add it to the low osc byte btfss STATUS,C ; Was there a carry? goto add4 ; No, add the next bytes incfsz osc_1,f ; Ripple carry up to the next byte goto add4 ; No new carry, add the next bytes incfsz osc_2,f ; Ripple carry up to the next byte goto add4 ; No new carry, add the next bytes incf osc_3,f ; Ripple carry up to the highest byte add4 movf fstep_1,w ; Get the second byte increment addwf osc_1,f ; Add it to the second osc byte btfss STATUS,C ; Was there a carry? goto add5 ; No, add the third bytes incfsz osc_2,f ; Ripple carry up to the next byte goto add5 ; No new carry, add the third bytes incf osc_3,f ; Ripple carry up to the highest byte add5 movf fstep_2,w ; Get the third byte increment addwf osc_2,f ; Add it to the third osc byte btfss STATUS,C ; Was there a carry? goto add6 ; No, add the fourth bytes incf osc_3,f ; Ripple carry up to the highest byte add6 movf fstep_3,w ; Get the fourth byte increment addwf osc_3,f ; Add it to the fourth byte goto cal_loop ; Stay in calibrate mode cal_done ; Calibration is complete. clrf ee_addr ; Write final value to EEPROM. movlw osc_0 ; Get starting address of cal values movwf FSR ; Store in pointer call update_EEPROM_cal ; Write contents to EEPROM. bcf PB_flags,PCAL_FLG ; Clear the calibrate bit return ; Return to the caller ; P ; ***************************************************************************** ; * * ; * 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 vfo_X_3 ... vfo_X_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. * ; * * ; *Revision: Version 2.2 Added support for AD9851 * ; * * ; ***************************************************************************** ; calc_dds_word clrf AD9850_0 ; Clear the AD9850 control word bytes clrf AD9850_1 ; clrf AD9850_2 ; clrf AD9850_3 ; clrf AD9850_4 ; 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 ; ; Copy the active vfo frequency to freq_3..0 movf vfo_pointer,w ; Get the pointer to vfo_X_0 movwf FSR ; Put this into FSR for indirect addressing movf INDF,w ; Get vfo_X_0 movwf freq_0 ; Store it for addition incf FSR,f ; Point to vfo_X_1 movf INDF,w ; Get byte movwf freq_1 ; Store it incf FSR,f ; Point to vfo_X_2 movf INDF,w ; Get the byte movwf freq_2 ; Store it incf FSR,f ; Point vfo_X_3 movf INDF,w ; Get it movwf freq_3 ; Store it IFDEF CVFO ; If this calculate is for calibrate routine , bypass adding IF offset btfsc PB_flags,PCAL_FLG ; Check if in calibrate mode goto offset_byp ; Yes, bypass ; ; IF Offset must be added to the frequency displayed to get correct ; conversion oscillator frequency. Do not change original (displayed) ; vfo frequency at vfo_X_3..0, but copy to freq_3..0 for calculations. ; Add the IF offset in ioff3..0 to the displayed frequency in freq_3..0. ; clrf temp movlw freq_0 ; Pointer to conversion frequency movwf FSR ; Into FSR for indirect addressing movf ioff_0,w ; Get low byte of the if_table_0 addwf INDF,f ; Add to the low byte of freq_0 ; There may be a carry out of this add... movlw 3 ; Propagate ripple carries over next 3 regs movwf count ; call ripple_carry ; This function handles it decf FSR,f ; Adjust FSR to point to decf FSR,f ; freq_1 movf ioff_1,w ; Get next byte of the IF offset addwf INDF,f ; Add to next byte of freq movlw 2 ; Propagate any ripple carries into movwf count ; the next two registers call ripple_carry ; decf FSR,f ; Point to freq_2 movf ioff_2,w ; Get next byte of IF offset addwf INDF,f ; Add to conversion freq. ; incf FSR,f ; Point to freq_3 btfss STATUS,C ; Any carry? goto off_step1 ; No, add last byte incf INDF,f ; Ripple carry up to the highest byte off_step1 movf ioff_3,w ; Get most significant byte of IF offset addwf INDF,f ; Add it to the most significant byte of freq ; The required output frequency (IF*Displayed) is now in freq_3..0 ENDIF ; ; Calculate the DDS FTW by multiplying frequency in freq_3..0 by oscillator in ; osc_temp_3..0 ; offset_byp movlw 0x20 ; Set count to 32 (4 osc bytes of 8 bits) movwf mult_count ; Keep running count movlw freq_0 ; Get pointer to freq_0 mult_loop movwf FSR ; Store conv_freq_0 pointer in FSR for indirect addr. bcf STATUS,C ; Start with Carry clear btfss osc_temp_0,0 ; Is bit 0 (Least Significant bit) set? goto noAdd ; No, don't need to add freq term to total movf INDF,w ; Yes, get the freq_0 term addwf AD9850_1,f ; and add it in to total btfss STATUS,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 incf FSR,f ; Point to freq_1 movf INDF,w ; Use the freq_1 term addwf AD9850_2,f ; Add freq term to total in correct position btfss STATUS,C ; Does this addition result in a carry? goto add8 ; No, continue with next freq term incfsz 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 incf FSR,f ; Point tofreq_2 movf INDF,w ; Use the freq_2 term addwf AD9850_3,f ; Add freq term to total in correct position btfss STATUS,C ; Does this addition result in a carry? goto add9 ; No, continue with next freq term incf AD9850_4,f ; Yes, add one and continue add9 incf FSR,f ; Point to freq_3 movf INDF,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 ; movlw freq_0 ; Get the pointer to freq_0 decfsz mult_count,f ; One more bit has been done. Are we done? goto mult_loop ; No, go back to use this bit #ifdef AD9850 movlw 0x00 ; No clock multiplier (AD9850) #endif #ifdef AD9851 movlw 0x01 ; Turn on 6x clock multiplier (AD9851) #endif movwf AD9850_4 ; Last byte to be sent ; Mult answer is in bytes freq_3 .. freq_0 return ; Done. ; ; ***************************************************************************** ; * * ; * Purpose: This routine calls calc_dds_word to obtain the Frequency Tuning * ; * Word (FTW) and and then sends the AD9850(1) FTW to the DDS chip * ; * using a serial data transfer. * ; * * ; * Input: AD9850_4 ... AD9850_0 * ; * * ; * Output: The DDS chip register is updated. * ; * * ; ***************************************************************************** ; send_dds_word call calc_dds_word ; calculate the data to send to AD9850 send_dds_word1 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,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 AD9850_4+1 ; Next byte (past the end) subwf FSR,w ; btfss STATUS,C ; If carry, last byte has been sent goto next_byte ; More to send bsf PORTB,DDS_load ; Send load signal to the AD9850 bcf PORTB,DDS_load ; return ; ; ; ***************************************************************************** ; * * ; * 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, 16x1 or 16x2 display. * ; * * ; * Input: None * ; * * ; * Output: None * ; * * ; ***************************************************************************** ; init_LCD call wait_64ms ; Wait for LCD to power up movlw CMD_RESET_LCD ; 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 CMD_RESET_LCD ; 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 CMD_RESET_LCD ; 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 PICEL_IFC1 ; Set the LCD for 4-bit mode I/O 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 PICEL_IFC2 ; Set operational characteristics of LCD call cmnd2LCD ; Send command in w to LCD movlw PICEL_DISPLAY_OFF ; Display off, cursor and blink off call cmnd2LCD ; Send command to LCD movlw CMD_CLEAR_LCD ; Clear and reset cursor call cmnd2LCD ; Send command in w to LCD movlw PICEL_CURSOR_RT ; Set cursor to move right, no display shift call cmnd2LCD ; Send command in w to LCD movlw PICEL_DISPLAY_ON ; Display on, cursor on, blink off call cmnd2LCD ; Send command to LCD return ; Done! ; ;**************************************************************************** ; ; Purpose: Displays power-up message and version information. ; ; Input: MCODE_REV_0 through MCODE_REV_4 set up ; ; Output: LCD displays debug info ; ; **************************************************************************** display_version movlw sign_msg_offset ; Offset to Sign-on message call display_message ; Display it on LCD IF LCDCHAR == 8 call wait_a_sec ; Let it sit a while movlw CMD_MOV_CUR_DISP ; Home the cursor call cmnd2LCD ELSE IF LCD16_LINEAR == 0 ; Move to 9th character position movlw PICEL_LONG_9 ; for AmQRP 16x1 LCD call cmnd2LCD ; Move cursor to appropriate position ENDIF ENDIF movlw ver_msg_offset ; Offset to version message call display_message ; Display on the LCD call wait_a_sec ; Yet another pause return ; back to caller ;******************************************************************************* ; ; Function: display_message ; ; Purpose: Displays the selected message in message_table on the LCD ; ; Input: Wreg contains the offset of the desired message to display ; ; Output: Message is displayed on LCD ; ; Revisions: New in Version 2.0 - W3CD ; ;******************************************************************************* display_message movwf message_pointer ; Save the starting message offset display_loop call message_table ; Call the table to return a character andlw FLAG_MASK ; Just set status flags, Z to be precise. btfsc STATUS, Z ; If the char is null, then end of message goto display_exit ; and exit call data2LCD ; Else, send the character to the LCD incf message_pointer,f ; Increment offset to next character movf message_pointer,w ; Get it into W goto display_loop ; Loop again display_exit return ; Exit when the message has been sent ; ; ***************************************************************************** ; ; Function: ; ; Purpose: This subroutine converts a 32 bit binary number to a 10 digit ; BCD number. The input value taken from vfo_X_(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 vfo_X_0 ... vfo_X_3 ; ; Output: The BCD number in BCD_0 ... BCD_4 ; ; Revisions: ; Rev. 2.0: Modified for use with dual vfos. 4-7-04 W3CD ; ; ***************************************************************************** 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 ; " " ; ; Unable to use indirect addressing here, so copy the active VFO ; data to freq3..0 for use by this function ; movf vfo_pointer,w ; Get vfo_X_0 address movwf FSR ; Copy address to FSR movf INDF,w ; Get low byte of VFO frequency movwf freq_0 ; store it for conversion incf FSR,f ; Point to vfo_X_1 movf INDF,w ; Get vfo_X_1 movwf freq_1 ; Store it incf FSR,f ; Point to vfo_X_2 movf INDF,w ; Get the byte movwf freq_2 ; Store it incf FSR,f ; Point to vfo_X_3 movf INDF,w ; Get it movwf freq_3 ; Store it ; Now ready for conversion bin_loop bcf STATUS,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,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,Z ; Is loop count now zero? goto adjust ; No, go to adjust return ; Yes, EXIT ; ============================================================================ ; 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.) ; adjust 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 P ; * If a 1x16 LCD display so display freq kHz - e.g 14,025.000 kHz P ; * or 14,025,000 Hz * ; * * ; * Input: The values in BCD_4 ... BCD_0 * ; * * ; * Output: The frequency is displayed on the LCD with leading zero * ; * suppression if that option is enabled. The suppressed zero is * ; * replaced with supr_char. * ; * * ; ***************************************************************************** ; show_freq call bin2BCD ; Convert the binary freq. to BCD for display movlw CMD_HOME_CUR_LCD ; 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 clrf supr_test ; Clear the suppression test flag ; ; 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 (0011XXXX) ; swapf BCD_3,w ; Swap 10MHz BCD digit into lower nibble of W call send_digit ; Check suppression and send byte to LCD ; ; Extract and send "YYYY" from byte containing "XXXXYYYY" ; - Mask with 0x0F to get 0000YYYY ; - Add offset for ASCII character set in LCD (0011YYYY) ; movf BCD_3,w ; Put 1MHz BCD digit into lower nibble of W call send_digit ; Check suppression and send byte 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 call send_digit ; Check suppression and send byte to LCD movf BCD_2,w ; Put 10KHz BCD digit into lower nibble of W call send_digit ; Check suppression and send byte to LCD swapf BCD_1,w ; Swap 1KHz BCD digit into lower nibble of W call send_digit ; Check suppression and send byte to LCD IF LCDCHAR == 16 IFDEF DISP_HZ movlw ',' ; Get a comma for Hertz display ELSE movlw '.' ; Use a period for KHz display ENDIF call data2LCD ; Send byte in W to LCD IF LCD16_LINEAR == 0 ; If using the AmQRP 16x1 LCD, movlw PICEL_LONG_9 ; point to LCD digit number nine call cmnd2LCD ; Send command byte in W to LCD ENDIF ENDIF movf BCD_1,w ; Put 100 Hz BCD digit into lower nibble of W call send_digit ; Check suppression and send byte to LCD swapf BCD_0,w ; Swap 10 Hz BCD digit into lower nibble of W call send_digit ; Check suppression and send byte to LCD ; The Least signicant character will not be suppressed movf BCD_0,w ; Put 1 Hz BCD digit into lower nibble of W andlw LOW_NYBBLE_MASK ; Mask for lower nibble only (0000YYYY) addlw ASCII_NUM_BASE ; Add offset for ASCII char set (0011YYYY) call data2LCD ; Send byte in W to LCD IF LCDCHAR == 16 IFDEF DISP_HZ movlw hz_offset ; Display "Hz" on LCD ELSE movlw khz_offset ; Display KHz on LCD ENDIF call display_message ; Position 15 & 16 is blank ENDIF call set_cursor_pos ; Indicate the lowest digit position ; being modified. - W3CD 3-26-04 return ; ; ================================================================================ ; Internal subroutine called by show_freq ; Leading zero suppression takes place if #DEFINE is enabled. ; To provide leading zero suppression of frequency display. If suppressing, and ; character is zero, character defined by supr_char is inserted until first ; non-zero character, then flag is set and suppression ends. ; send_digit andlw LOW_NYBBLE_MASK ; Mask for lower nibble only (0000YYYY) IFDEF ZERO_SUPR btfsc STATUS,Z ; Is char a zero goto supr_zero ; Yes btfsc supr_test,0 ; No, has a non-zero character been detected? goto supr_exit ; Yes, send the zero character to LCD incf supr_test,f ; No, set non-zero char processed goto supr_exit ; Go to exit and send the zero to LCD supr_zero btfsc supr_test,0 ; Has a non-zero character been processed? goto supr_exit ; Yes, send character ; Zero character and still supressing movlw supr_char ; Load the suppression char goto supr_done ; And send to the LCd supr_exit ENDIF addlw ASCII_NUM_BASE ; Add offset for ASCII char set (0011YYYY) supr_done ; Period or original char to the LCD goto data2LCD ; Send to display, return is to show_freq ; ; ***************************************************************************** ; ; Function: show_band ; ; Purpose: Displays the band number of the active vfo on the LCD at the ; current display position. If LCD is 2 X 16, band is always ; displayed on the second line. ; ; Input: vfo_select ; ; Output: Current band number is indicated on the LCD display ; ; Revisions: ; Ver 2.0: New function. 4-4-04 W3CD. ; Ver CBE: 16x2 LCD - Band number displayed on second line. ; ; ***************************************************************************** show_band IFDEF LCD_16x2 movlw PICEL_LONG_9+LCD_POS_10 ; Point at digit 9 of second line call cmnd2LCD ; send command to LCD movlw band_msg_offset ; Get band message pointer call display_message ; Display "BAND " ENDIF clrf BCD_0 ; Clear the conversion target clrf BCD_1 ; variables movf vfo_a_band,w ; Guess that VFO A is active btfsc vfo_select,VFO_SEL ; Determine active VFO movf vfo_b_band,w ; If it's B, then get the band index ; movwf temp ; Save band number in temp incf temp,f ; Increment the band number so that ; band number starts with 1 movlw TEN ; Put a value of 10 into W subwf temp,w ; Subtract 10 from band. Result in W btfss STATUS, C ; If carry is clear, then band >= 10 goto band_ones ; If so, then process just the 1's digit ; Else, band is greater than 10 movwf BCD_0 ; Put the difference in the 1's digit incf BCD_1,f ; Add 1 to the 10's digit goto display_band ; skip ahead band_ones movf temp,w ; Get the band number, since it's less than 10 movwf BCD_0 ; Store it in BCD_0. movlw ' ' ; Get a space character call data2LCD ; Diplay a space for 10's digit goto disp_band_ones ; Now display the 1's digit display_band movf BCD_1,w ; Get the 10's digit addlw ASCII_NUM_BASE ; Add ASCII '0' call data2LCD ; Send to LCD disp_band_ones ; movf BCD_0,w ; Get the 1's digit addlw ASCII_NUM_BASE ; Add ASCII digit base call data2LCD ; Send to LCD IFNDEF LCD_16x2 call wait_a_sec ; Display band number for 1 sec ENDIF return ; Back to caller ; ; ***************************************************************************** ; ; Function: set_cursor_pos ; ; Purpose: Position cursor at the frequency digit position being updated. ; ; Input: The cur_pos variable holds the digit position. ; ; Output: None ; ; Revisions: ; Ver 2.0: Added function to handle cursor placement on the ; LCD. 3-26-04 W3CD. ; Ver 2.01: Fixed AmQRP 16x1 LCD cursor addressing bug ; 4/20/04 W3CD. ; Ver 1.2 Added support for 16x2 LCD ; ; ***************************************************************************** set_cursor_pos ; This function depends on the LCD ; display being used. If it's 8 ; characters wide, then operation is ; straightforward. It gets slightly ; more involved for 16 AmQRP character LCDs. IF LCDCHAR == 8 movlw CMD_MOV_CUR_DISP ; Cursor position base command iorwf cur_pos,w ; OR in the cursor position ELSE IF LCD16_LINEAR == 1 movlw CMD_MOV_CUR_DISP ; Cursor position base command iorwf cur_pos,w ; Or in the cursor postion ELSE IF LCD16_LINEAR == 2 movlw CMD_MOV_CUR_DISP ; Cursor position base command iorwf cur_pos,w ; Or in the cursor postion ELSE movlw LCD_POS_9 ; Get 9th cursor position subwf cur_pos,w ; Determine where the cursor is btfsc STATUS,C ; Carry is clear if cur_pos < 9th position goto set_cur_1 ; If cur_pos >= 8, then make some adustments ; Else, use cursor address as-is movlw CMD_MOV_CUR_DISP ; Get positioning command addwf cur_pos,w ; Include the cursor position goto set_cur_2 ; Finish up set_cur_1 ; Arrive here with delta cursor pos in W addlw PICEL_LONG_9 ; Add command & offset for 9th position set_cur_2 ; Continue with cursor movement ENDIF ENDIF ENDIF call cmnd2LCD ; Move the cursor return ; ; ; ***************************************************************************** ; ; Function: clear_lcd ; ; Purpose: Clears the LCD and homes the cursor. ; ; Input: None ; ; Output: None ; ; Revisions: ; Ver 2.0: Added function for convenience and to reduce code space ; 4-4-04 W3CD. ; ; ***************************************************************************** clear_lcd movlw CMD_CLEAR_LCD ; Prepare to display a new message on LCD call cmnd2LCD ; by clearing the current one 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 P ; * all others outputs P ; ***************************************************************************** ; busy_check clrf PORTB ; Clear all outputs on PORTB banksel TRISB ; Switch to bank 1 for Tristate operation movlw b'00001000' ; Set RB3 input, others outputs movwf TRISB ; via Tristate banksel PORTB ; 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,Z ; Is loop counter down to zero? goto not_busy ; If yes, return regardless 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) banksel TRISB ; 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 banksel PORTB ; 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 HI_NYBBLE_MASK ; 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 LOW_NYBBLE_MASK ; 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 HI_NYBBLE_MASK ; Set up mask andwf PORTB,f ; Clear old RB3..RB0 movf LCD_char,w ; Move LS nibble of into W andlw LOW_NYBBLE_MASK ; 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 IFDEF LCD_16x2 ; **************************************************************************** ; ; Purpose: This routine will display selected VFO on 2nd line if LCD is 16 x 2. ; ; Input: ; ; Output: "VFO_A" of "VFO-B" displayed on 2nd line of LCD ; ;***************************************************************************** disp_vfo ; If this is 2 X 16 LCD movlw PICEL_LONG_9 ; Point at digit 0 of second line call cmnd2LCD ; send command to LCD movlw vfoa_offset ; Assume VFO-A in use btfss vfo_select,VFO_SEL ; Determine which vfo is active goto vfo_disp ; Its A movlw vfob_offset ; No, its VFO-B, point to VFO-B message vfo_disp call display_message ; and send to LCD return ; Go back ENDIF IFDEF RESTORE_B_F ;??????????????????????????????????????????????????????????????????????????? ; **************************************************************************** ; ; Purpose: Process Timer 1 interrupts. Count required number of interrupts ; and then save the current band number and frequency, in EEPROM. ; The band and frequency will be used on the next power up as the ; start up band and frequency. ; ; Input: Enter from poll_encoder. T1_INT indicator, int_counter ; ; Output: If int_counter expired, present band nember and frequency is ; saved in EEPROM restore_band and frequency saved in EEPROM ; restore_freq. ; ;****************************************************************************** process_int decfsz int_counter,f ; Decrement the interrupt counter goto int_exit ; Not zero, exit movlw restore_band_a ; EEPROM adddress for VFO A band movwf ee_addr movf vfo_a_band,w ; Band info for VFO A call write_EEPROM ; Write VFO A band in EEPROM movlw restore_f_a ; Point at startup frequency save location movwf ee_addr ; Write frequency to EEPROM. movlw vfo_a_0 ; Get starting address of vfo A movwf FSR ; Store in pointer call update_EEPROM_cal ; Write contents to EEPROM. IFDEF DUAL_VFO movlw restore_band_b ; Address of VFO-B band save movwf ee_addr ; Set for writing EEPROM movf vfo_b_band,w ; Get VFO B band call write_EEPROM ; Save VFO-B band data movlw restore_f_b ; Point at startup frequency save location movwf ee_addr ; Write frequency to EEPROM. movlw vfo_b_0 ; Get starting address of vfo B movwf FSR ; Store in pointer call update_EEPROM_cal ; Write contents to EEPROM. ENDIF movlw count_of_ints ; Initialize the counter movwf int_counter ; to start over int_exit bcf int_flags,T1_INT ; Reset the interrupt flag bit goto proc_int_rtn ; Back to poll_encoder ENDIF ; ; **************************************************************************** ; ; Purpose: This routine will save the current frequency band in EEPROM. ; This band 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 while in band change ; mode. The band of the current VFO in use is used. ; ; Input: vfo_select, vfo_a_band, vfo_b_band ; ; Output: none ; ; Revisions: ; Ver 2.0: New function. 4-4-04 W3CD ; ; **************************************************************************** update_EEPROM_band movlw startup_band ; Get startup address movwf ee_addr ; and set up for start of EEPROM writes ; Determine which VFO is active movf vfo_a_band,w ; Guess at VFO A btfsc vfo_select,VFO_SEL ; Check the select bit movf vfo_b_band,w ; If it's B, then get the band value ; Band data in w call write_EEPROM ; Store it in EEProm return ; Done. ; **************************************************************************** ; ; Function: update_EEPROM_freq, update_EEPROM_cal ; ; Purpose: This routine saves the current VFO frequency in EEPROM, using ; the current VFO's band. The calibrate function updates EEPROM ; by entering at the second address. ; ; Input: vfo_select ; ; Output: none ; ; Revisions: ; Ver 2.0: New function. 4-4-04 W3CD ; ; **************************************************************************** update_EEPROM_freq movlw BAND_ADJUST ; Offset to vfo_X_band addwf vfo_pointer,w ; Calc address of vfo_X_band movwf FSR ; Prepare for indirect addressing movf INDF,w ; Get the band number movwf temp ; Save band in temp movlw band_table ; Get the base address of EEPROM band table addwf temp,w ; Add in 4*band to addwf temp,w ; obtain the proper addwf temp,w ; table offset addwf temp,w ; movwf ee_addr ; Store EEPROM table entry start address incf FSR,f ; Increment to point to vfo_X_0 update_EEPROM_cal ; Entry point for storing new ref_osc bytes movlw FREQ_COUNT ; Prepare loop to write contents movwf count ; of active VFO to EEPROM update_EE_loop movf INDF,w ; Get vfo frequency byte call write_EEPROM ; Do the write thing incf FSR,f ; Bump vfo freq pointer incf ee_addr,f ; Increment the EEPROM address decfsz count,f ; Decrement byte count goto update_EE_loop ; Iterate while bytes remain to be written ; Fall through when done return ; Done! ; ***************************************************************************** ; ; Function: read_EEPROM_freq ; ; Purpose: Reads a 4-byte frequency value from EEPROM ; ; Input: FSR points to the LSB into which the frequency is copied ; ee_addr is setup with the proper frequency table pointer ; ; Output: The 4-byte target is updated with EEPROM frequency ; ; Revisions: ; Ver 2.0: Created 4-7-04 by W3CD. ; Ver 2.1: Updated for PIC16F628A 10-03-05 W3CD ; ; ***************************************************************************** read_EEPROM_freq movlw FREQ_COUNT ; Get the byte count movwf count ; into loop counter read_EEPROM_loop call read_EEPROM ; Read a byte. Data returned in w movwf INDF ; Store new frequency byte incf ee_addr,f ; Increment EEPROM read address incf FSR,f ; Point to next target address decfsz count,f ; Decrement byte count goto read_EEPROM_loop ; Loop if more bytes remaining. return ; Back to caller ; ***************************************************************************** ; * * ; * Purpose: Write the byte of data at EEDATA to the EEPROM at address * ; * EEADR. * ; * * ; * Input: w contains the data to be written. * ; * ee_addr contains the write address. * ; * * ; * Output: The EEPROM value is updated. * ; * * ; * Revisions: PEGEN 2.1: Updated for PIC16F628A 10-03-05 W3CD * ; * * ; ***************************************************************************** write_EEPROM banksel EEDATA ; Switch to bank 1 movwf EEDATA ; Store the write data movf ee_addr,w ; Get the write address movwf EEADR ; Prepare EE address bsf EECON1,WREN ; Set the EEPROM write enable bit movlw 0x55 ; Write 0x55 and 0xAA to EEPROM movwf EECON2 ; control register, as required movlw 0xAA ; for the write movwf EECON2 ; bsf EECON1,WR ; Set WR to initiate write bit_check btfsc EECON1,WR ; Has the write completed? goto bit_check ; No, keep checking bcf EECON1,WREN ; Clear the EEPROM write enable bit banksel PORTA ; Switch to bank 0 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. * ; * * ; * Revisions: PEGEN 2.1: Updated for PIC16F628A. 10-03-05 W3CD * ; * * ; ***************************************************************************** read_EEPROM banksel EEADR ; Switch to bank 1 movf ee_addr,w ; Get read address movwf EEADR ; Copy to EE register bsf EECON1, RD ; Request the read movf EEDATA,w ; Get the data banksel PORTA ; Switch to bank 0 return ; Return to the caller ; ***************************************************************************** ; * * ; * Purpose: Wait for a specified number of milliseconds. * ; * * ; * Entry point wait_a_sec: Wait for 1 second P ; * Entry point wait_256ms: Wait for 256 msec P ; * 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 * ; * Entry point wait_1ms * ; * * ; * Input: None * ; * * ; * Output: None * ; * * ; ***************************************************************************** ; wait_a_sec ; ****** Entry point ****** call wait_256ms ; call wait_256ms ; wait_512ms ; ****** Entry point ****** 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 goto outer_loop ; Into the wait loops wait_1ms movlw 0x2 movwf timer1 ; ; 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 0xA5 ; Set up inner loop counter movwf timer2 ; to 165 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