; **************************************************************************** ; * PICELgen - Signal Generator (VFO) with Direct Digital Synthesis * ; * Version 2.10 * ; * October 3, 2005 * ; * * ; **************************************************************************** ; Description: ; This is the control program for a DDS VFO built with an AD9850 DDS chip, a ; shaft encoder, three push button switches and a Hitatchi 44780-compatible ; Liquid crystal 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 ; ; 25/4/12 PICELgen2.10b: ; Modified to include AD9851 VK5TM ; ; 29/4/12 PICELgen2.10d: ; Removed LED Code. Added 'A' & 'B' to VFO frequency ; display. ; ;***************************************************************************** ; P ; Target Controller - PIC16F84A in PIC-EL board 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--------NC P ; +5V-----------!MCLR |4 15| OSC2--------NC 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 radix dec #include P16F628A.inc ; Include register and memory definitions errorlevel 0, -205, -220, -302 ; Skip nuisance messages ; ; **************************************************************************** ; * Configuration fuse information: * ; **************************************************************************** __config _CP_OFF & _LVP_OFF & _BODEN_OFF & _PWRTE_ON & _WDT_OFF & _INTOSC_OSC_NOCLKOUT ;***************************************************************************** ; ; PIC-EL Hardware Configurations ; ;***************************************************************************** ; ; 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. Select the appropriate #define to handle the appropriate LCD ; ; ; Select only one of the following 3 #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 ; ; The following definitions depend upon the 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 ; 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 VFOs. 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. ;!!! #define DUAL_VFO ; ; Keep the Version number here ; #define CODE_VERSION "2.10" ; ; ***************************************************************************** ; * 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 oscillator frequencies are as follows: ; ; Frequency ref_osc_3 ref_osc_2 ref_osc_1 ref_osc_0 ; ; 180.00 MHz 0x17 0xDC 0x65 0xDF ; 125.00 MHz 0x22 0x5C 0x17 0xCA ; 120.00 MHz 0x23 0xCA 0x98 0xCE ; 100.00 MHz 0x2A 0xF3 0x1D 0xC4 ; 90.70 MHz 0x2F 0x5A 0x82 0x7A ; 66.66 MHz 0x40 0x6E 0x52 0xE7 ; 66.00 MHz 0x41 0x13 0x44 0x5F ; 50.00 MHz 0x55 0xE6 0x3B 0x88 ; ; To calculate other values: ; ref_osc_3 = (2^32 / oscillator_freq_in_Hertz). ; ref_osc_2, ref_osc_1, and ref_osc_0 are the fractional part of ; (2^32 / oscillator_freq_in_Hertz) times 2^24. ; Note: 2^32 = 4294967296 and 2^24 = 16777216 ; ; For example, for a 120 MHz clock: ; ref_osc_3 is (2^32 / 120 x 10^6) = 35.791394133 truncated to 35 (0x23) ; ref_osc_2 is the high byte of (.791394133 x 2^24) = 13277390.32 ; 13277390.32 = 0xCA98CE, so high byte is CA. ; ref_osc_1 is the next byte of 0xCA98CE, or 98 ; ref_osc_0 is the last byte of 0xCA98CE, or CE ; ;==== Currently set 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 ;==== Currently set for 125 MHz Oscillator ======= Modified 22/2/12 TM ;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 ;==== Currently set for 180 MHz Oscillator ======= For AD9851 Modified 22/2/12 TM 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 0xDF ; Least significant byte ; 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. ;limit_3 equ 0x01 ; Most significant byte for 30 MHz ;limit_2 equ 0xC9 ; Next byte ;limit_1 equ 0xC3 ; Next byte ;limit_0 equ 0x80 ; Least significant byte ;-------------------------------------------------------------------------------------------------------- ; Modified 22/2/12 TM - 40MHz upper Frequency limit (AD9850) ; 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. ;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 ;--------------------------------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------------------------------- ; Modified 22/2/12 TM - 60MHz upper Frequency limit (AD9851) ; 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. 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 ;--------------------------------------------------------------------------------------------------------- ; 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 ; The following defines the default decade increment at power up. Chose ; any value between 0 and 7 to customize your version. default_decade equ 0 ;############ ;************************************************************************************* ; ; General definitions ; ;************************************************************************************* EEPROM_BASE equ 0x2100 ; Where EEPROM begins in the 16F84A 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 ;#################################################################### ;************************************************************************************** ; 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 ]CURSOR POSITION???? 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 ;************************************************************************************** ; ; PIC-EL LED Control bits and related definitions ; ;************************************************************************************** ;!!!LED1_ON equ 0x07 ; Mask AND'd with PORTB to turn on LED1 ;!!!LED2_ON equ 0x0b ; Mask AND'd with PORTB to turn on LED2 ;!!!LED3_ON equ 0x0d ; Mask AND'd with PORTB to turn on LED3 ;!!!LED1_OFF equ 0x08 ; Value OR'd with PORTB to turn off LED1 ;!!!LED2_OFF equ 0x04 ; Value OR'd with PORTB to turn off LED2 ;!!!LED3_OFF equ 0x02 ; Value OR'd with PORTB to turn off LED3 ;!!!LEDS_OFF equ 0x0e ; Port B bits to turn off all LEDs ;!!!LED_MASK equ 0xf1 ; Port B mask to isolate led control bits ;***************************************************************************** ; ; 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 ;!!! PICEL_IFC2 equ CMD_SET_IFC_2 + IFC2_4_BIT + IFC2_2_LINES + IFC2_FONT_5_7 ;!!! ELSE ;!!! PICEL_IFC2 equ CMD_SET_IFC_2 + IFC2_4_BIT + IFC2_1_LINES + IFC2_FONT_5_7 ;!!! ENDIF ;!!! ELSE PICEL_IFC2 equ CMD_SET_IFC_2 + IFC2_4_BIT + IFC2_1_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. ;!!!PICEL_LONG_9 equ CMD_MOV_CUR_DISP + OFFSET_9 ;!!!LCD_POS_9 equ 8 ; Cursor position 9 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 ; ; **************************************************************************** ; * ID location information: * ; * (MPASM warns about DW here, don't worry) * ; **************************************************************************** ; ORG 0x2000 DATA 0x57 DATA 0x33 DATA 0x43 DATA 0x44 ; ; ; **************************************************************************** ; * 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 ;##################### DATA 0x02 ; Band with which the PIC-EL is booted ;################### ;########################################################################### ;***************************************************************************** ; ; 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. QRP CW frequencies for each band. ; Data is expressed in Hz and is stored LSB first. ; ;***************************************************************************** band_loc band_table equ band_loc - EEPROM_BASE DATA 0x50, 0x9e, 0x1b, 0x00 ; Band 0 1.810 MHz DATA 0x40, 0x52, 0x36, 0x00 ; Band 1 3.560 MHz DATA 0x00, 0x6c, 0x6b, 0x00 ; Band 2 7.040 MHz DATA 0xa0, 0x5b, 0x9a, 0x00 ; Band 3 10.116 MHz DATA 0xe0, 0x89, 0xd6, 0x00 ; Band 4 14.060 MHz DATA 0x80, 0x1f, 0x14, 0x01 ; Band 5 18.096 MHz DATA 0xa0, 0x59, 0x41, 0x01 ; Band 6 21.060 MHz DATA 0x10, 0x09, 0x7c, 0x01 ; Band 7 24.906 MHz DATA 0x60, 0x29, 0xac, 0x01 ; Band 8 28.060 MHz ; ; Some popular IF frequencies ; DATA 0xe0, 0x44, 0xa3, 0x00 ; Band 9 10.700 MHz - FM receivers DATA 0xc8, 0x22, 0x56, 0x00 ; Band 10 5.645 MHz - Drake 4 series DATA 0x38, 0xff, 0x4a, 0x00 ; Band 11 4.915 MHz - QRP rigs DATA 0xb8, 0xcd, 0x33, 0x00 ; Band 12 3.395 MHz - Heathkit SB series DATA 0x58, 0xf1, 0x06, 0x00 ; Band 13 455 KHz - Lots!!! band_end_loc num_bands equ (band_end_loc - band_loc) / 4 DATA 0x00, 0x00, 0x00 ; Clear the last 3 bytes ;############################################################################### ; ; **************************************************************************** ; * Allocate variables in general purpose register space * ; **************************************************************************** ; CBLOCK RAM_BASE ; Start Data Block freq_0 ; Display frequency (hex) freq_1 ; (4 bytes) freq_2 ; Used by bin2BCD only freq_3 BCD_0 ; Display frequency (BCD) BCD_1 ; (5 bytes) BCD_2 BCD_3 BCD_4 AD9850_0 ; AD9850 control word AD9850_1 ; (5 bytes) AD9850_2 AD9850_3 AD9850_4 fstep_0 ; Frequency inc/dec fstep_1 ; (4 bytes) fstep_2 ; update_decade fills these in fstep_3 ; with values from decade_table BCD_count ; Used in bin2BCD routine BCD_temp ; " mult_count ; Used in calc_dds_word bit_count ; " byte2send ; osc_0 ; Current oscillator osc_1 ; (4 bytes) osc_2 osc_3 osc_temp_0 ; Oscillator frequency osc_temp_1 ; (4 bytes) osc_temp_2 ; Used by calibrate osc_temp_3 LCD_char ; Character being sent to the LCD LCD_read ; Character read from the LCD timer1 ; Used in delay routines timer2 ; " ren_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. ;!!!led_states ; Used to store which leds are on. ENDC ; End of Data Block CBLOCK RAM_ALL ; Common RAM base ee_addr ; Storage for eeprom address ENDC ; ; **************************************************************************** ; * The 16F84 resets to 0x00. * ; * The Interrupt vector is at 0x04. (Unused) * ; **************************************************************************** ; ORG 0x0000 reset_entry goto start ; Jump around the band table to main program ;**************************************************************************** ; ; 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 get 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 ;INCREMENT? OR DECREMENT? TO FIX CURSOR POSITION??? ;!!! dt 11, 10, 9, 7, 6, 5, 3, 2 ;???? ;!!! 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 "SV3ORA", 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 ;khz_msg ; dt " KHz",0 ; KHz display for 16 char lcd only ;khz_offset equ khz_msg - messages ; ;calibrate_msg ; Displayed during CAL mode only ; dt "CAL ", 0 ; and only on 16 character LCDs ;cal_offset equ calibrate_msg - messages ;!!! 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 default_3 ... * ; * definitions above, and relies on the reference oscillator * ; * constant defined in ref_osc_3 ... ref_osc_0. * ; * * ; * Output: Normal VFO operation. * ; * * ; ***************************************************************************** ; start clrf INTCON ; No interrupts for now movlw 0x07 ; Set the 3 LSBs of CMCON movwf CMCON ; to enable normal I/O clrf RCSTA ; Disable USART Receiver bsf STATUS,RP0 ; 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 bcf STATUS,RP0 ; Switch back to bank 0 ; Initialize DDS Module with zero freq - added 01/04/2013 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 ;!!! movlw LEDS_OFF ; Turn off LEDS at the start ;!!! movwf led_states 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 ; ; 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 ; ; 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 ; 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 ; Initialize other variables. movlw default_decade ; Get the default decade increment movwf decade ; Store in decade clrw ; Prepare W for update call update_decade ; Init fstep3..0 and cur_pos ; 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 call clear_lcd ; Clear the display movlw band_msg_offset ; Get the band message offset into W call display_message ; Send message to LCD. call show_band ; Display the band number call wait_512ms ; Display band number for 1/2 second ;!!! movlw LED1_ON ; Turn on LED1 for VFO A active ;!!! movwf led_states ; ; Display the power-on frequency. ; call show_freq ; COnvert to BCD and display it ; ; Send power on-frequency to the DDS chip. ; call send_dds_word ; Send the power-on frequency to the ; AD9850 in serial mode call send_dds_word ; Send it twice. Sometimes needed for ; a clean start-up ; ; Fall into the Main Program Loop ; ; ***************************************************************************** ; * * ; * Purpose: This is the Main Program Loop. The program's main loop * ; * calls poll_encoder, which continuously polls the rotary shaft * ; * encoder. When the shaft encoder has changed, the direction * ; * it moved is determined and stored in last_dir. The subroutine * ; * then returns to main. * ; * * ; * If the tuning-increment pushbutton (PIC-EL PB_1) is not pressed,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 (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 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 movlw band_msg_offset ; Get the band message offset into W call display_message ; Send message to LCD. call show_band ; Display the band number call wait_512ms ; Display band number for 1/4 second - 29/4/12 changed to 1/2sec ; Now display the frequency call get_band ; Read the band frequency from EEPROM call show_freq ; Display the frequency on the LCD 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 ; ; 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 movf vfo_pointer,w ; Get pointer to vfo_X_0 addlw BAND_ADJUST ; Subtract offset to get vfo_X_band movwf FSR ; store it in file select register movlw band_table ; Get starting offset of EEPROM band table addwf INDF,w ; Add the band number * 4 addwf INDF,w ; to get the addwf INDF,w ; proper offset addwf INDF,w ; into the band table movwf ee_addr ; Prepare EEPROM address ; incf FSR,f ; Now point to vfo_X_0 call read_EEPROM_freq ; Get the frequency return ; Done! ; ***************************************************************************** ; ; Function: add_step ; ; Purpose: This routine adds the 32 bit value of fstep to the 32 bit ; value in freq. When incrementing, the fstep value is a ; positive integer. When decrementing, fstep is the complement ; of the value being subtracted. ; ; Input: The 32 bit values in fstep and freq ; ; Output: The sum of fstep and freq is stored in freq. When ; incrementing this value may exceed the maximum. When ; decrementing, it may go negative. ; ; 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 ; ; Check the most significant byte. ; movlw limit_3 ; Get high limit value 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 movlw limit_2 ; Second limit byte 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 movlw limit_1 ; Third limit byte 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 movlw limit_0 ; Fourth limit byte 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. ; movlw limit_0 ; Get low byte of limit movwf INDF ; Store in vfo_X_0 incf FSR,f ; Point to vfo_X_1 movlw limit_1 ; Get next limit byte movwf INDF ; Store it in vfo_X_1 incf FSR,f ; Point to vfo_X_2 movlw limit_2 ; Get the next limit byte movwf INDF ; Store it in vfo_X_2 incf FSR,f ; Point to vfo_X_3 movlw limit_3 ; Get the high byte of limit 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 freq. ; ; Output: The updated value in freq. ; ; 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 addressing 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 ;***************************************************************************** ; ; 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 ; ;***************************************************************************** ; ; 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 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. ;!!! movlw LED3_ON ; Turn on LED3 to indicate band mode ;!!! andwf led_states,f ; Update the states goto pe_exit ; Finish up pe_no_band bcf PB_flags,BAND_MODE ; Clear the band mode flag ;!!! movlw LED3_OFF ; Clear the band mode LED and exit ;!!! iorwf led_states,f ; Update led_states pe_exit ;!!! call set_leds ; turn it on 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 ;!!! movlw LED3_OFF ; Turn off the band mode indicator ;!!! iorwf led_states,f ; Set the led state bit ;!!! call set_leds ; Turn LED3 off 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 call show_band ; Display band being modified call wait_a_sec ; Show it for 1 second before pb_update_exit 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 ;!!! movlw LED2_OFF ; Turn off VFO B indicator ;!!! iorwf led_states,f ;!!! movlw LED1_ON ; Turn on VFO A indicator ;!!! andwf led_states,f 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 ;!!! movlw LED1_OFF ; Turn off VFO A indicator ;!!! iorwf led_states,f ;!!! movlw LED2_ON ; Turn on VFO B indicator ;!!! andwf led_states,f process_pb3_exit ; Finish up PB_3 handling clrf PB_flags ; Clear all PB flags call show_freq ; Display the frequency on the LCD 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 ; 16 Character LCDs cal_loop call send_dds_word ; Update the DDS chip call poll_encoder ; Wait until the encoder has moved. clrf fstep_3 ; Clear the three most significant clrf fstep_2 ; bytes of fstep clrf fstep_1 ; movlw 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 btfss PORTA,PB_1 ; Tuning-increment pushbutton pressed? goto cal_loop ; Yes, stay in calibrate mode ; Else, 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 freq_3 ... freq_0. The reference * ; * oscillator value is treated as a fixed point real, with a 24 * ; * bit mantissa. * ; * * ; * Output: The result is stored in AD9850_3 ... AD9850_0. * ; * * ; ***************************************************************************** ; calc_dds_word clrf AD9850_0 ; Clear the AD9850 control word bytes clrf AD9850_1 ; clrf AD9850_2 ; clrf AD9850_3 ; clrf AD9850_4 ; movlw 0x20 ; Set count to 32 (4 osc bytes of 8 bits) movwf mult_count ; Keep running count movf osc_0,w ; Move the four osc bytes movwf osc_temp_0 ; to temporary storage for this multiply movf osc_1,w ; (Don't disturb original osc bytes) movwf osc_temp_1 ; movf osc_2,w ; movwf osc_temp_2 ; movf osc_3,w ; movwf osc_temp_3 ; ; Now develop the active vfo frequency pointer movf vfo_pointer,w ; Get pointer to vfo_X_0 mult_loop movwf FSR ; Store vfo_X_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 vfo_X_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 vfo_X_1 movf INDF,w ; Use the vfo_X_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 to vfo_X_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 vfo_X_3 movf INDF,w ; Use the vfo_X_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 ; movf vfo_pointer,w ; Get the pointer to vfo_X_0 decfsz mult_count,f ; One more bit has been done. Are we done? goto mult_loop ; No, go back to use this bit ; clrf AD9850_4 ; Yes, clear _4. Answer is in bytes _3 .. _0 movlw 0x01 ; Turn on 6x clock multiplier (AD9851)Added TM 26/2/12 (comment out for AD9850) movwf AD9850_4 ; Last byte to be sent Added TM 26/2/12 (comment out for AD9850) return ; Done. ; ; ***************************************************************************** ; * * ; * Purpose: This routine sends the AD9850 control word to the DDS chip * ; * using a serial data transfer. * ; * * ; * Input: AD9850_4 ... AD9850_0 * ; * * ; * Output: The DDS chip register is updated. * ; * * ; ***************************************************************************** ; send_dds_word 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 ; goto next_byte ; bsf PORTB,DDS_load ; Send load signal to the AD9850 bcf PORTB,DDS_load ; ;!!! call set_leds 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 or a 16x1 display. P ; * * ; * 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: bin2BCD ; ; Purpose: This subroutine converts a 32 bit binary number to a 10 digit ; BCD number. The input value taken from freq(0 to 3) is ; preserved. The output is in BCD(0 to 4), each byte holds => ; (hi_digit,lo_digit), most significant digits are in BCD_4. ; This routine is a modified version of one described in ; MicroChip application note AN526. ; ; Input: The value in freq_0 ... freq_3 ; ; Output: The BCD number in BCD_0 ... BCD_4 ; ; 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 ; * * ; * Input: The values in BCD_4 ... BCD_0 * ; * * ; * Output: The number displayed on the LCD * ; * * ; ***************************************************************************** ; 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 ;****************************************************************************** ;; Test if VFO A or B and put letter 'A' or 'B' in postion 1 ; movlw 'A' ; Guess that VFO A is active ; btfsc vfo_select,VFO_SEL ; Determine active VFO ; movlw 'B' ; If it's B, then display B ; call data2LCD ; to position 1 of LCD ; movlw ' ' ; space to next position ; call data2LCD ; ; ; Running 4-bit mode, so need to send Most Significant Nibble first. ; ; Extract and send "XXXX" from byte containing "XXXXYYYY" ; - Swap halves to get YYYYXXXX ; - Mask with 0x0F to get 0000XXXX ; - Add ASCII bias (0030XXXX) ; swapf BCD_3,w ; Swap 10MHz BCD digit into lower nibble of W andlw LOW_NYBBLE_MASK ; Mask for lower nibble only (0000XXXX) addlw ASCII_NUM_BASE ; Add offset for ASCII char set (0030XXXX) call data2LCD ; Send byte in W to LCD ; ; Extract and send "YYYY" from byte containing "XXXXYYYY" ; - Mask with 0x0F to get 0000YYYY ; - Add offset for ASCII character set in LCD (0030YYYY) ; movf BCD_3,w ; Put 1MHz BCD digit into lower nibble of W andlw LOW_NYBBLE_MASK ; Mask for lower nibble only (0000YYYY) addlw ASCII_NUM_BASE ; Add offset for ASCII char set (0030YYYY) call data2LCD ; Send byte in W to LCD ;;!!! IF LCDCHAR == 16 ; movlw ',' ; Get a comma ; call data2LCD ; Send byte in W to LCD ;;!!! ENDIF swapf BCD_2,w ; Swap 100KHz BCD digit into lower nibble of W andlw LOW_NYBBLE_MASK ; Mask for lower nibble only (0000XXXX) addlw ASCII_NUM_BASE ; Add offset for ASCII char set (0030XXXX) call data2LCD ; Send byte in W to LCD movf BCD_2,w ; Put 10KHz BCD digit into lower nibble of W andlw LOW_NYBBLE_MASK ; Mask for lower nibble only (0000YYYY) addlw ASCII_NUM_BASE ; Add offset for ASCII char set (0030YYYY) call data2LCD ; Send byte in W to LCD swapf BCD_1,w ; Swap 1KHz BCD digit into lower nibble of W andlw LOW_NYBBLE_MASK ; Mask for lower nibble only (0000XXXX) addlw ASCII_NUM_BASE ; Add offset for ASCII char set (0030XXXX) call data2LCD ; Send byte in W to LCD ;;!!! IF LCDCHAR == 16 ; movlw '.' ; Get a period ; call data2LCD ; Send byte in W to LCD ;!!! 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 andlw LOW_NYBBLE_MASK ; Mask for lower nibble only (0000YYYY) addlw ASCII_NUM_BASE ; Add offset for ASCII char set (0030YYYY) call data2LCD ; Send data byte in W to LCD swapf BCD_0,w ; Swap 10 Hz BCD digit into lower nibble of W andlw LOW_NYBBLE_MASK ; Mask for lower nibble only (0000XXXX) addlw ASCII_NUM_BASE ; Add offset for ASCII char set (0030XXXX) call data2LCD ; Send data byte in W to LCD movf BCD_0,w ; Put 1 Hz BCD digit into lower nibble of W andlw LOW_NYBBLE_MASK ; Mask for lower nibble only (0000YYYY) addlw ASCII_NUM_BASE ; Add offset for ASCII char set (0030YYYY) call data2LCD ; Send byte in W to LCD ;;!!! IF LCDCHAR == 16 ; movlw khz_offset ; Display "KHz" on LCD ; call display_message ; Position 16 is blank ;!!! ENDIF call set_cursor_pos ; Indicate the lowest digit position ; being modified. - W3CD 3-26-04 ;!!! call set_leds ; Establish led states return ; ; ; ***************************************************************************** ; ; Function: show_band ; ; Purpose: Displays the band number of the active vfo on the LCD at the ; current display position ; ; Input: vfo_select ; ; Output: Current band number is indicated on the LCD display ; ; Revisions: ; Ver 2.0: New function. 4-4-04 W3CD. ; ; ***************************************************************************** show_band 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. ; Now display it. display_band movf BCD_1,w ; Get the 10's digit addlw ASCII_NUM_BASE ; Add ASCII '0' call data2LCD ; Send to LCD ; movf BCD_0,w ; Get the 1's digit addlw ASCII_NUM_BASE ; Add ASCII digit base call data2LCD ; Send to LCD ;!!! call set_leds ; Establish led states 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. ; ; ***************************************************************************** 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 ;!!! 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 call cmnd2LCD ; Move the cursor ;!!! call set_leds ; Restore LED inidications 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 bsf STATUS,RP0 ; Switch to bank 1 for Tristate operation movlw b'00001000' ; Set RB3 input, others outputs movwf TRISB ; via Tristate bcf STATUS,RP0 ; Switch back to bank 0 bcf PORTB,LCD_rs ; Set up LCD for Read Busy Flag (RS = 0) bsf PORTB,LCD_rw ; Set up LCD for Read (RW = 1) movlw 0xFF ; Set up constant 255 movwf timer1 ; for timer loop counter LCD_is_busy bsf PORTB,LCD_e ; Set E high movf PORTB,w ; Read PORTB into W movwf LCD_read ; Save W for later testing bcf PORTB,LCD_e ; Drop E again nop ; Wait a nop ; while bsf PORTB,LCD_e ; Pulse E high (dummy read of lower nibble), nop ; wait, bcf PORTB,LCD_e ; and drop E again decf timer1,f ; Decrement loop counter btfsc STATUS,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) bsf STATUS,RP0 ; Switch to bank 1 for Tristate operation movlw 0x00 ; Set up to enable PORTB data pins movwf TRISB ; All pins (RB7..RB0) are back to outputs bcf STATUS,RP0 ; Switch to bank 0 bcf PORTB,LCD_rw ; Set LCD back to Write mode (RW = 0) bcf PORTB,LCD_rs ; Guess RS should be clear btfsc rs_value,0 ; Should RS be clear? (is bit 0 == 0?) bsf PORTB,LCD_rs ; No, set RS ; ; Transfer Most Significant nibble (XXXX portion of XXXXYYYY) ; movlw 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 ; **************************************************************************** ; ; 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 LOW 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 bsf STATUS,RP0 ; 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 bcf STATUS,RP0 ; 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 bsf STATUS,RP0 ; 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 bcf STATUS,RP0 ; Switch to bank 0 return ; Return to the caller ; ***************************************************************************** ; ; Function: set_leds ; ; Purpose: Restores the static LED states on Port B after display updates ; and DDS daughter card writes. ; ; Input: led_states ; ; Output: LEDs are restored to their static values ; ; Revisions: ; Ver 2.0 Added function 4-6-04 W3CD ; ; ***************************************************************************** ;!!!set_leds ;!!! movlw LED_MASK ; Get led mask ;!!! andwf PORTB,F ; Apply it to Port B ;!!! movf led_states,w ; Get the led states ;!!! iorwf PORTB,f ; Apply them to Port B ;!!! return ; Done ; ***************************************************************************** ; * * ; * 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 0xFF ; Set up inner loop counter movwf timer2 ; to 255 inner_loop decfsz timer2,f ; Decrement inner loop counter goto inner_loop ; If inner loop counter not down to zero, ; then go back to inner loop again decfsz timer1,f ; Yes, Decrement outer loop counter goto outer_loop ; If outer loop counter not down to zero, ; then go back to outer loop again return ; Yes, return to caller ; ; ***************************************************************************** ; END