; **************************************************************************** ; SweepGen - Swept Frequency Signal Generator using the AmQRP PIC-EL ; board together with the NJQRP DDS daughtercard. ; ; Version 1.1 October 4, 2005 by Bob Okas, W3CD (w3cd@arrl.net) ; ; Description: ; ; This is the control program for a DDS-based Sweep Generator using the ; AmQRP PIC-EL board and DDS daughter card. It can be used as a fixed- ; frequency signal generator or as a sweep frequency generator. The user ; programs the start, end, step and marker frequencies. ; ; SweepGen is based on PICELGen 2.01 by the same author. The following ; comments are included to provide details of this program's forebears. ; I indeed stand on the shoulders of giants. ; ; Version 1.1 is a port of the previous version to the PIC16F628A. ; Version 1.0 incorporates all of the improvements and changes from ; the previous versions of PICELGen and modifies the original code base ; to reduce program memory usage. Comments in the code are provided to ; note these changes. ; ; ;****************************************************************************** ; 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 ; 1) Change to use 1x8 LCD instead of 1x16 ; - Freq displayed as Hz (e.g. 14025000) ; - CAL freq displayed as Hz (e.g. 10000000) ; 2) Set up to support the NJQRP DDS Daughterboard ; - 50 MHz oscillator ; 3) Change PORTB pin allocations to support PIC-EL ; - Change RB4-RB7 to RB0-RB3 ; - Change RB0 (DDS LOAD) to RB7 ; - Change RB1 (LCD_rs) to RB6 ; - Change RB2 (LCD_rw) to RB5 ; - Change RB3 (LCD_e) to RB4 ; - Change Busy_check routine to check correct bit ; - Change cmnd2LCD/data2LCD routine - swap nibbles ; 4) Change RA2 to always be an LOW output ; 5) Support pushbutton on RA3 instead of encoder ; shaft switch for bandswitch and calibrate ; 6) Support pushbutton on RA4 for fast tuning ; ; 1/3/04 PICELgen1.1 Add Title and Version on start-up ; 1/7/04 PICELgen1.2 Restructure main loop and change_band ; 2/2/04 PICELgen1.2a Fix confusing comment in the header re shaft sw. ; Fix comment in header regarding PB_2 for calib. ; 2/8/04 PICELgen1.3 Fix for reliable startup of DDS ; Add code for 1 MHz steps (up or down) ; Add code to save new start-up frequency ; 3/12/04 PICELgen1.4 Remove use of watchdog timer (temp sensitivity) ; Add code to support either 1x8 or 1x16 LCDs ; - Use #DEFINE to select the LCD type ; Fix calibrate routine so it stays in cal_loop ; ; ; 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/4/05 SweepGen 1.1: ; Ported ver 1.0 code to the 16F628A. W3CD ; ; 26/2/2012 SweepGen 1.1a Terry Mowles VK5TM ; ; 1. Updated to use AD8950 or AD9851. Currently set for AD9851 ; see code at end of calc_dds_word for change. ; 2. Ref Osc changed to 125MHz for AD9850 or 180MHz for AD9851 ; 3. Max frequency changed to 40MHz for AD9850, 60MHz for AD9851 ; !!! COMMENT and UNCOMMENT code for respective chip !!! ; ; 13/5/2012 SweepGen 1.1e ; ; Changed to internal 4MHz clock. Gives 2 free ports on Port A ; Removed a lot of extraneous code comments and unused code. ; LED code removed. ; ; 1/04/2013 SweepGen 1.1e ; ; Added initialise DDS to zero freq at start up to prevent hash ; generation as per datasheet - Terry Mowles VK5TM ; ;***************************************************************************** ; ; Target Controller - PIC16F628A in PIC-EL board ; __________ ; PB_3------------RA2 |1 18| RA1---------ENCODER A ; PB_2-Bandswitch-RA3 |2 17| RA0---------ENCODER B ; PB_1-Tuning etc-RA4 |3 16| OSC1--------SPARE ; +5V-----------!MCLR |4 15| OSC2--------SPARE ; Ground----------Vss |5 14| VDD---------+5 V ; LCD11-----------RB0 |6 13| RB7---------DDS_LOAD ; LCD12-----------RB1 |7 12| RB6---------LCD_rs (LCD Pin 4) ; LCD13/DDS_CLK---RB2 |8 11| RB5---------LCD_rw (LCD Pin 5) ; LCD14/DDS_DATA--RB3 |9 10| RB4---------LCD_e (LCD Pin 6) ; ---------- ; ; **************************************************************************** ; * Device type and options. * ; **************************************************************************** ; processor PIC16F628A radix dec #include P16F628A.inc ; Include register and memory definitions errorlevel 0, -205, -207, -220, -302 ; Skip nuisance messages ; **************************************************************************** ; * Configuration fuse information: ; **************************************************************************** __config _CP_OFF & _LVP_OFF & _BODEN_OFF & _PWRTE_ON & _WDT_OFF & _INTOSC_OSC_NOCLKOUT ; ; The Software Version number is kept here ; #define CODE_VERSION "1.1e " ; ; ***************************************************************************** ; * 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 ;************************************************************************************* ; ; General definitions ; ;************************************************************************************* EEPROM_BASE equ 0x2100 ; Where EEPROM begins in the 16F628 RAM_BASE equ 0x20 ; Where General Purpose Regs. begin in 16F628 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. 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 FREQ_COUNT equ 4 ; Number of frequency bytes to copy or loop over DECADE_MASK equ 0x07 ; Applied to decade adjustments SMALL_FSTEP equ 0x04 ; Small cal. freq. step. About .4 Hz LARGE_FSTEP equ 0x80 ; Large cal. freq. step. About 3 Hz PORTA_CONTROL equ 0xfb ; Port A is all inputs except RA2 DECADE_0 equ 0 ; 1 Hz adjustment value DECADE_1 equ 1 ; 10 Hz adjustment value DECADE_2 equ 2 ; 100 HZ DECADE_3 equ 3 ; 1 KHz DECADE_4 equ 4 ; 10 KHz DECADE_5 equ 5 ; 100 KHz DECADE_6 equ 6 ; 1 MHz DECADE_7 equ 7 ; 10 MHz ;************************************************************************************** ; 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 PB_CS_FLG equ 1 ; Indicates that cal or sweep mode is enabled PB_1_PRESS equ 2 ; Indicates that PB_1 was pressed PB_2_PRESS equ 3 ; Indicates that PB_2 was pressed F_SET equ 4 ; Indicates that PB_2 were pushed DECADE_DECREMENT equ 0xff ; Value which decrements cursor frequency decade DECADE_INCREMENT equ 0x01 ; Value which increments cursor frequency decade PB_RPT_WAIT equ 32 ; 1 second delay before autorepeat PB_RPT_DLY equ 4 ; 1/8 second between repeats PB_INIT_FREQ_WAIT equ 64 ; 2 second delay before new start freq set ;***************************************************************************** ; ; 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 PICEL_IFC2 equ CMD_SET_IFC_2 + IFC2_4_BIT + IFC2_1_LINES + IFC2_FONT_5_7 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. ; ; **************************************************************************** ; * 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: ; MARKER equ 0x02 ; Tones bit = RA2 PB_2 equ 0x03 ; Multi-purpose pushbutton PB_1 equ 0x04 ; Tuning-increment/Calibrate Pushbutton ; ; **************************************************************************** ; * ID location information: * ; * (MPASM warns about DW here, don't worry) * ; **************************************************************************** ; ORG 0x2000 DATA 0x57 ;'W' DATA 0x33 ;'3' DATA 0x43 ;'C' DATA 0x44 ;'D' ; **************************************************************************** ; * 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 EE_ref_osc_loc DATA ref_osc_0, ref_osc_1, ref_osc_2, ref_osc_3 ;***************************************************************************** ; ; Programmable Frequency Memories. The following EEPROM locations ; store several frequency variables. Data is expressed in Hz and ; is stored LSB first. ; ;***************************************************************************** EE_cal_freq_loc DATA cal_freq_0, cal_freq_1, cal_freq_2, cal_freq_3 EE_start_freq_loc DATA 0x80, 0x84, 0x1e, 0x00 ; 2.000 MHz EE_end_freq_loc DATA 0x20, 0x0b, 0x20, 0x00 ; 2.100 MHz EE_step_freq_loc DATA 0x64, 0x00, 0x00, 0x00 ; 100 Hz Step EE_marker_freq_loc DATA 0xd0, 0x47, 0x1f, 0x00 ; Marker @ 2.05 MHz ; ; Develop offsets from start of EEPROM for use ; by the program ; EE_ref_osc equ EE_ref_osc_loc - EEPROM_BASE EEcal_freq equ EE_cal_freq_loc - EEPROM_BASE EEstart_freq equ EE_start_freq_loc - EEPROM_BASE EEend_freq equ EE_end_freq_loc - EEPROM_BASE EEstep_freq equ EE_step_freq_loc - EEPROM_BASE EEmarker_freq equ EE_marker_freq_loc - EEPROM_BASE ; ; **************************************************************************** ; * Allocate variables in general purpose register space * ; **************************************************************************** ; CBLOCK RAM_BASE ; Start Data Block BCD_0 ; Display frequency (BCD) BCD_1 ; (5 bytes) BCD_2 BCD_3 BCD_4 temp_freq_0 ; Temporary frequency storage temp_freq_1 ; (4 bytes) temp_freq_2 ; Used by bin2BCD, calibrate temp_freq_3 ; and calc_dds_word osc_0 ; Current oscillator osc_1 ; (4 bytes) osc_2 osc_3 AD9850_0 ; AD9850/51 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 start_freq_0 ; Sweep start frequency start_freq_1 start_freq_2 start_freq_3 end_freq_0 ; Sweep end frequency end_freq_1 end_freq_2 end_freq_3 step_freq_0 ; Frequency Step size step_freq_1 step_freq_2 step_freq_3 marker_freq_0 ; Frequency at which to set RA2 marker_freq_1 marker_freq_2 marker_freq_3 BCD_count ; Used in bin2BCD routine BCD_temp ; " bit_count ; Used in calc_dds_word byte2send ; LCD_char ; Character being sent to the LCD LCD_read ; Character read from the LCD timer1 ; Used in delay routines timer2 ; " ren_new ; New value of encoder pins A and B ren_old ; Old value of encoder pins A and B ren_read ; Encoder pins A and B and switches last_dir ; Indicates last direction of encoder next_dir ; Indicates expected direction count ; Multipurpose loop counter rs_value ; The LCD rs line flag value PB_flags ; Pushbutton flags PB_wait_count ; PB repeat control timer temp ; Miscellaneous variable decade ; Frequency decade subject to updating cur_pos ; Cursor position: 0-7 for 8 char LCD fstruct_ptr ; Pointer to frequency data struct in pgm memory message_pointer ; Temp message pointer variable enc_counter ; Divide by 4 counter for mechanical encoder freq_pointer ; pointer to current frequency being modified. ;!!!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 16F628 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 ;**************************************************************************** ; ; Frequency Data Structures ; ; Collecting the data for the sweep parameters is so repetitious that ; it made sense to write just one function to do the actual data collection, ; thus saving program space. The particulars of the data to collect are ; stored in what amounts to an array of structures, a look-up table if you ; will, that contains the collection function parameters. Each structure ; contains 3 elements: ; 1. Which LED(s) to illuminate ; 2. What message to flash on the LCD ; 3. What decade to start the frequency adjustment ; 4. Starting RAM location used to hold the frequency ; ;**************************************************************************** ;**************************************************************************** ; Modified TM 28/4/2012 MSG_OFFSET equ 0 ; Offset to prompt msg pointer element DEC_OFFSET equ 1 ; Offset to decade element RAM_OFFSET equ 2 ; Offset to frequency storage ;**************************************************************************** f_struct_table addwf PCL,f ; Jump to the appropriate table entry to return f_start_struct_loc dt start_freq_msg, DECADE_3, start_freq_0 f_end_struct_loc dt end_freq_msg, DECADE_3, end_freq_0 f_step_struct_loc dt step_freq_msg, DECADE_0, step_freq_0 f_marker_struct_loc dt marker_freq_msg, DECADE_3, marker_freq_0 ; ; Prepare the table offsets for use by the program ; f_start equ f_start_struct_loc - f_start_struct_loc f_end equ f_end_struct_loc - f_start_struct_loc f_step equ f_step_struct_loc - f_start_struct_loc f_marker equ f_marker_struct_loc - f_start_struct_loc ;**************************************************************************** ; ; 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 dt 10, 9, 8, 6, 5, 4, 2, 1 ;**************************************************************************** ; ; 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_loc ; Displayed at power-on dt "SweepGen", 0 ver_msg_loc ; Displayed at power-on dt " Ver", CODE_VERSION, 0 start_freq_msg_loc dt "Start", 0 end_freq_msg_loc dt "End", 0 step_freq_msg_loc dt "Step", 0 marker_freq_msg_loc dt "Marker", 0 sweep_msg_loc dt "Sweeping",0 end_err_msg_loc dt "Bad End",0 ; ; Develop offsets to messages in table ; sign_on_msg equ sign_on_msg_loc - messages ver_msg equ ver_msg_loc - messages start_freq_msg equ start_freq_msg_loc - messages end_freq_msg equ end_freq_msg_loc - messages step_freq_msg equ step_freq_msg_loc - messages marker_freq_msg equ marker_freq_msg_loc - messages sweep_msg equ sweep_msg_loc - messages end_err_msg equ end_err_msg_loc - messages khz_msg_loc dt " KHz",0 ; KHz display for 16 char lcd only calibrate_msg_loc ; Displayed during CAL mode only dt "CAL ", 0 ; and only on 16 character LCDs khz_msg equ khz_msg_loc - messages cal_msg equ calibrate_msg_loc - messages ;***************************************************************************** ; ; Purpose: This is the start of the program. Program and hardware ; initialization takes place here before the execution falls ; into the main program loop. ; ; Input: PB_1 determines if calibration is to be performed. ; ; Output: None. ; ; Revisions: ; Ver 1.0: Copied from PICELGen 2.0 and modified for use here ; 4-20-04 W3CD. ; Ver 1.1: Adapted for PIC16F628A. 10-04-05 W3CD ; ;***************************************************************************** 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 clrf AD9850_0 ; AD9850/51 control word clrf AD9850_1 ; (5 bytes) clrf AD9850_2 clrf AD9850_3 clrf AD9850_4 call send_dds_word ; Send it to the DDS call send_dds_word ; twice to be sure bsf PORTA,MARKER ; Set marker bit high call init_LCD ; Initialize the LCD call display_version ; Display title and version number ; ; Read reference oscillator data from EEPROM ; movlw osc_0 ; Get osc_0 address movwf FSR ; save for indirect addressing movlw EE_ref_osc ; Get EEPROM ref_osc offset into W 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 applying power ; btfss ren_read,PB_1 ; Tuning-increment/Cal pushbutton pressed? call calibrate ; Yes, calibrate ; Else, continue with rest of initialization call read_sweep_freqs ; Read the stored frequencies from EEPROM ; and set freq_pointer to start_freq_0 ; ***************************************************************************** ; ; Function: main ; ; Purpose: This is the Main Program Loop. This section collects the ; starting and ending sweep frequencies, the frequency increment ; and the marker frequency from the user. This information is ; then stored in EEPROM for later use. When all of the parameters ; have been collected, the sweep function is called to perform ; the frequency sweeps. When the user presses the appropriate ; button on the PICEL board, the sweep terminates and control ; returns back to main. ; ; Input: No explicit inputs. ; ; Output: No explicit outputs. ; ; Revisions: ; Ver 1.0 New function 4-20-04 W3CD ; ;********************************************************************* main movlw f_start ; Starting frequency structure call get_frequency ; Get the starting frequency get_end movlw f_end ; Ending frequency structure call get_frequency ; Get the ending frequency call check_end ; Make sure that end > start btfss STATUS,C ; If carry is set, then all's well. goto get_step ; Go get the next parameter ; Else, there is a problem. call clear_lcd ; Erase LCD movlw end_err_msg ; Flash error message call display_message ; call wait_512ms ; Display for 1/2 second goto get_end ; Go back and try again. get_step movlw f_step ; Step size structure call get_frequency ; Collect the data movlw f_marker ; Marker frequency structure call get_frequency ; Collect that information call write_sweep_freqs ; Store the user-specified freqs in EEPROM call sweep ; Sweep until button is pressed call read_sweep_freqs ; Read the sweep frequencies from EEPROM call get_decade ; Restore fstep3..0 goto main ; Go back Jack and do it again. ;****************************************************************************** ; ; Function: get_frequency ; ; Purpose: Get the user-specified frequency ; ; Inputs: W points to the data structure which contains the ; pertinent data ; User enters the frequency using the shaft encoder. ; PB3 is pressed to accept the displayed value. ; ; Outputs: The LCD displays the current frequency. ; The DDS is updated as the frequency selection changes. ; X_freq_3..0 contain the current frequency data when ; PB3 is pressed. ; ; Revsisions: ; Ver 1.0: New function 4-21-04 W3CD ; ;******************************************************************************* get_frequency movwf fstruct_ptr ; Save pointer to current frequency data call clear_lcd ; Wipe the display movf fstruct_ptr,w ; Get the struct pointer addlw MSG_OFFSET ; Add in the message pointer offset call f_struct_table ; Get the pointer to the prompt message call display_message ; Display the prompt message call wait_512ms ; Show if for a while ; Now get the default adjustment decade movf fstruct_ptr,w ; Get the struct pointer addlw DEC_OFFSET ; Add offset to point to decade to adjust call f_struct_table ; Retreive the value movwf decade ; Store in decade variable clrw ; Prepare W for decade adjust call update_decade ; Position cursor and select decade increment call clear_lcd ; Erase contents movf fstruct_ptr,w ; Get the struct pointer addlw RAM_OFFSET ; Add in frequency data offset call f_struct_table ; Get the pointer to frequency data storage movwf freq_pointer ; Save this for the following operations call show_freq ; Show the frequency call update_dds ; Send it to the DDS ; call send_dds_word ; twice to be sure get_freq_1 call poll_encoder ; Wait for user to do something btfsc PB_flags,F_SET ; Check if PB was pushed. goto get_freq_2 ; If so, then finish up ; Else, encoder was adjusted call step ; Adjust the frequency goto get_freq_1 ; Loop until setting is committed. get_freq_2 bcf PB_flags,F_SET ; Clear the Freq Set flag return ;****************************************************************************** ; ; Function: check_end ; ; Purpose: Compares the start and end frequencies. If end freq is ; greater than start freq, the Carry flag is clear. Otherwise, ; it is set, indicating that end_freq <= start_freq. ; ; Inputs: start_freq and end_freq contain the user-specified data ; ; Outputs: Carry flag ; ; Revsisions: ; Ver 1.0: New function 4-26-04 W3CD ; ;****************************************************************************** check_end movf start_freq_0,w ; Copy the end_freq values movwf AD9850_0 ; into AD9850 registers to movf start_freq_1,w ; perform the comparison. movwf AD9850_1 ; This doesn't bother the movf start_freq_2,w ; DDS data because it will movwf AD9850_2 ; be recalculated before movf start_freq_3,w ; these registers are movwf AD9850_3 ; needed again. ; movlw end_freq_3 ; Point to the start_freq MSB movwf FSR ; Tuck it away into FSE call check_freq_status ; Calculate start_freq - end_freq return ; Return the Carry flag. ;****************************************************************************** ; ; Function: sweep ; ; Purpose: Produces a swept frequency output from start_freq to end_freq. ; It also sets high the PIC RA2 output bit when the marker ; frequency has been met and exceeded. This marker bit is cleared ; when the sweep returns to start_freq. ; ; This function precalculates the DDS data for start_freq, ; end_freq, step_freq and marker_freq to avoid using calc_dds_word ; in the loop as this function is costly in terms of time. ; ; Inputs: start_freq_3..0, end_freq_3..0, step_freq_3..0, ; marker_freq_3..0, PB_flags ; ; Outputs: DDS produces swept frequency output until the user presses the ; appropriate pushbutton to stop. ; ; Revisions: ; Rev 1.0: New function. 4-21-04 W3CD ; ;******************************************************************************* sweep ; Prepare for sweeping bsf PB_flags,PB_CS_FLG ; Set Cal/Sweep flag to skip some PB handling ; movlw step_freq_0 ; Get frequency increment address movwf freq_pointer ; Store it in freq_pointer call calc_dds_word ; Convert it to DDS data in AD9850_3..0 movlw fstep_0 ; Copy target address is fstep_3..0 call copy_dds_word ; Copy AD9850_3..0 to fstep movlw end_freq_0 ; Get address of end frequency call convert_to_dds ; Convert to DDS and store it back movlw marker_freq_0 ; Get address of marker frequency call convert_to_dds ; Convert to DDS & store it back call clear_lcd ; Inform the user that sweeping is movlw sweep_msg ; begun. call display_message sweep_start ; Where the sweeping begins bcf PORTA,MARKER ; Clear the marker bit to start movlw start_freq_0 ; Get the starting frequency pointer movwf freq_pointer ; Store it in freq_pointer call calc_dds_word ; Calculate the DDS word ; AD9850_4..0 are our working registers, movlw AD9850_0 ; so copy start address to freq_pointer movwf freq_pointer ; sweep_loop call pb_2_check ; Examine user input. btfsc PB_flags,F_SET ; Check if user requested end of sweeping goto sweep_end ; If so, then exit ; Else, do another iteration call send_dds_word ; Send the frequency to DDS movlw marker_freq_3 ; Get last address of marker frequency movwf FSR ; into FSR call check_freq_status ; Check if it's time to raise the marker btfsc STATUS,C ; If carry is clear, then marker freq reached bsf PORTA, MARKER ; Set marker to indicate that freq was reached call add_step ; Add the frequency increment ; to current AD9850 data movlw end_freq_3 ; Point to the ending DDS frequency word movwf FSR ; Store in FSR call check_freq_status ; test if we've reached the end limit btfsc STATUS,C ; If carry clear, then goto sweep_start ; end was reached. Restart the sweep goto sweep_loop ; Else, loop again sweep_end clrf PB_flags ; Clear the PB flags on exit bsf PORTA,MARKER ; Set the marker bit high at end return ;****************************************************************************** ; ; Function: convert_to_dds ; ; Purpose: Converts the 4-byte frequency value pointed to by W ; into DDS-compatible data and store it back into the ; supplied frequency address. Support function for sweep. ; ; Inputs: W points to X_freq_0 ; ; Outputs: X_freq_3..0 contain the DDS-compatible data ; ; Revisions: ; Rev 1.0: New function. 4-21-04 W3CD ; ;******************************************************************************* convert_to_dds movwf freq_pointer ; Store address in W into freq pointer call calc_dds_word ; Convert freq to DDS word ; AD9850_3..0 are relevant since _4 is always 0 ; Copy this data to X_freq_3..0 movf freq_pointer,w ; Get the target address of copy call copy_dds_word ; Copy the DDS data return ; Back to caller ;****************************************************************************** ; ; Function: check_freq_status ; ; Purpose: Compares the frequency registers pointed to by FSR against ; the AD98503..0 bytes. ; ; Inputs: FSR points to X_freq_3 data to be tested ; AD9850_3..0 contain the current DDS word ; ; Outputs: Carry flag is set if X_freq_3..0 >= AD9850_3..0, ; Else, Carry is clear ; ; Revisions: ; Rev 1.0: New function. 4-22-04 W3CD ; ;******************************************************************************* check_freq_status movf INDF,w ; Get the X_freq_3 value into w subwf AD9850_3,w ; W = AD9850_3 - X_freq_3 btfss STATUS,C ; Check Carry goto check_freq_end ; If clear, then freq_3 < AD_3. Done ; Else, Carry set, so btfss STATUS,Z ; are the bytes equal? goto check_freq_end ; No, so freq_3 > AD_3. Done ; Else, both MSBs are equal decf FSR,f ; Point to X_freq_2 movf INDF,w ; W <- X_freq_2 subwf AD9850_2,w ; W = AD9850_2 - X_freq_2 btfss STATUS,C ; Check carry goto check_freq_end ; If clear, then freq_3,2 < AD_3,2. Done ; Else, Carry set, so btfss STATUS,Z ; are the bytes equal? goto check_freq_end ; Nope, so freq_2 > AD_2. Done ; Else, those bytes were the same decf FSR,f ; Point to X_freq_1 movf INDF,w ; W <- X_freq_1 subwf AD9850_1,w ; W = AD9850_1 - X_freq_1 btfss STATUS,C ; Check the Carry status goto check_freq_end ; If C clear, then freq_3..1 < AD_3..1 ; Else, carry is set. btfss STATUS,Z ; Are the bytes equal? goto check_freq_end ; No, so X_freq is greater than AD9850. Done ; Else, the previous pairs were equal decf FSR,f ; Point to X_freq_0 movf INDF,w ; W <- X_freq_0 subwf AD9850_0,w ; W = AD9850_0 - X_freq_0 ; If Carry is clear, then ; X_freq_3..0 < AD9850_3..0 ; Else, a set Carry flag indicates that ; X_freq_2..0 >= AD9850_3..0 check_freq_end ; Return with Carry flag indicating return ; the results of the comparison ;****************************************************************************** ; ; Function: copy_dds_word ; ; Purpose: Copies the AD0950_3..0 bytes to the frequency registers ; pointed to by FSR ; ; Inputs: FSR points to X_freq_0 destination ; AD9850_3..0 contain the current DDS word to be copied ; ; Outputs: X_freq_3..0 contain AD9850_3..0 data ; ; Revisions: ; Rev 1.0: New function. 4-22-04 W3CD ; ;******************************************************************************* copy_dds_word movwf FSR ; Put pointer into FSR for indirect addr movf AD9850_0,w ; Get LSB movwf INDF ; Store in frequency register incf FSR,f ; bump pointer movf AD9850_1,w ; Get next byte movwf INDF ; Store it incf FSR,f movf AD9850_2,w ; Get next byte movwf INDF ; Store it incf FSR,f movf AD9850_3,w ; Get MSB movwf INDF ; Store it return ; Back to caller ;****************************************************************************** ; ; 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. ; ; Inputs: last_dir ; ; Outputs: Adjusts the operating frequency up or down, based on last_dir. ; the operating frequency is specified by freq_pointer, which ; is implicitly used here. ; ; Revisions: ; Rev 1.0: Copied from PICELGen 2.0 and adapted for use here ; 4-21-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 update_dds ; Send the new control word to the DDS chip return ; Back to caller. ; ***************************************************************************** ; ; 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 the value pointed to by ; freq_pointer ; ; Output: The sum of fstep and the value pointed to by freq_pointer ; is stored in freq. When incrementing this value may exceed ; the maximum. When decrementing, it may go negative. ; ; Revisions: ; Rev 1.0: Copied from PICELGen Rev 2.0. Modified for use with ; any frequency variable 4-21-04 W3CD. ; ; ***************************************************************************** add_step clrf temp ; Clear the temp variable ; movf freq_pointer,w ; Get the pointer to X_freq_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 ; X_freq_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 X_freq_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 X_freq_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 1.0: Copied from PICELGen 2.0. 4-20-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 1.0: Copied from PICELGen 2.0 and modified for use with ; any frequency variable. 4-21-04 W3CD ; ; ***************************************************************************** check_add ; First, get the address of X_freq_3 movlw TABLE_ADJUST ; Offset to 4th element addwf freq_pointer,w ; Add in the pointer to X_freq_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 X_freq_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 X_freq_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 X_freq_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 freq_pointer,w ; Get pointer to X_freq_0 movwf FSR ; into indirect addressing reg. ; movlw limit_0 ; Get low byte of limit movwf INDF ; Store in X_freq_0 incf FSR,f ; Point to X_freq_1 movlw limit_1 ; Get next limit byte movwf INDF ; Store it in X_freq_1 incf FSR,f ; Point to X_freq_2 movlw limit_2 ; Get the next limit byte movwf INDF ; Store it in X_freq_2 incf FSR,f ; Point to X_freq_3 movlw limit_3 ; Get the high byte of limit movwf INDF ; Store it in X_freq_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: ; Rev 1.0: Copied from PICELGen 2.0 4-20-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 freq_pointer,w ; Get address of X_freq_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 X_freq_3 decf FSR,f ; Next byte clrf INDF ; Clear X_freq_2 decf FSR,f ; Next byte clrf INDF ; Clear X_freq_1 decf FSR,f ; Last byte clrf INDF ; Clear X_freq_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: ; Rev 1.0 Copied from PICELGen 2.0 4-20-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 1.0: Copied from PICELGen 2.0 and modified for use here ; 4-20-04 W3CD ; ;***************************************************************************** poll_encoder call process_pb ; Check if there is pushbutton activity btfsc PB_flags,F_SET ; If the F_SET bit is set, then done goto pe_exit ; movf PORTA,w ; Get the current encoder value movwf ren_read ; Save it ;********** Code for mechanical 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 ;**************************************************************** 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. ;============================================================================= ; Encoder bits are on RA0 and RA1 - the two low order bits of ren_new ; A and B are "gray code" - 90 degrees out of phase (quadrature) ; ___ ___ ; | | | | ; A ____| |___| |___ ; ___ ___ ; | | | | ; B ___| |___| |___ ; ^ ^ ^ ^ ^ ^ ^ ^ ; a b c d a b c d ; ; A B ; At point a: 0 0 ; At point b: 1 0 ; At point c: 1 1 ; At point d: 0 1 ; ; Going UP, the sequence is a,b,c,d,a,b,c,d, etc. so the sequence is: ; 00, 10, 11, 01, 00, 10, 11, 01, etc. ; ; Going DOWN, the sequence is d,c,b,a,d,c,b,a, etc. so the sequence is: ; 01, 11, 10, 00, 01, 11, 10, 00, etc. ; ; To determine if the sequence is UP or DOWN: ; 1) Take the "Right-Bit" of any pair. ; 2) XOR it with the "Left-Bit" of the next pair in the sequence. ; 3) If the result is 1 it is UP ; If the result is 0 it is DOWN ; ; The direction flag is 0 (DOWN) or 2 (UP) because of bit positioning ;============================================================================= bcf STATUS,C ; Clear the carry bit to prepare for rotate rlf ren_old,f ; Rotate old bits left to align "Right-Bit" movf ren_new,w ; Set up new bits in W xorwf ren_old,f ; XOR old (left shifted) with new bits movf ren_old,w ; Put XOR results into W also andlw UP_BIT ; Mask to look at only "Left-Bit" of pair movwf next_dir ; Save result (in W) as direction (bit=UP) xorwf last_dir,w ; See if direction is same as before ; ; Prevent encoder slip from giving a false change in direction. ; ;************** Code for optical encoder *************************************** ; btfsc STATUS,Z ; Zero flag set? (i.e, is direction same?) ; goto pe_continue ; Yes, same direction so no slip; keep going ; movf next_dir,w ; No Zero-flag, so direction changed ; movwf last_dir ; Update the direction indicator ; movf ren_new,w ; Save the current encoder bits (now in W) ; movwf ren_old ; for next time ; goto poll_encoder ; Try again ;******************************************************************************* pe_continue clrf last_dir ; Clear last_dir (default is DN) btfsc ren_old,DIR_BIT ; Are we going UP? goto pe_up ; Yes, go process it. ; Else, we are goiong down ;********** Code for mechanical 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 ;**************************************************************** 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 ;********** Code for mechanical 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 ;**************************************************************** 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 ; pe_exit return ; Return to the caller ;**************************************************************************** ; ; Function: process_pb ; ; Purpose: This function examines the state of PB_1 and PB_2 ; and has mulitple operating characteristics. ; ; 1. Pressing and releasing PB1 alone moves the cursor left ; one position and increments the decade to be adjusted ; by the shaft encoder. The cursor and frequency decade ; wrap around the 10 MHz position to the 1 Hz position. ; ; 2. Pressing and holding PB1 for more than 1 second starts ; the auto-repeat function and the decade are updated ; continuously until PB1 is released. The update rate ; is 4 times/sec. ; ; 3. Pressing PB2 alone sets the F_SET flag. This tells ; higher order functions that the selected frequency ; has been accepted, or in the case of active sweeping, ; causes the sweep to terminate. ; ; 4. Pressing PB2 while PB1 is held down moves the cursor ; to the right and the frequency decade is decreased ; accordingly. ; ; 5. Holding PB1 and PB2 down for more than 1 second enables ; the auto-repeat function and the cursor moves to the ; right at a rate of 4 times/sec. ; ; ; Inputs: PORTA bits PB_1 and PB_2 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 1.0: Copied from PICELGen 2.0 and modified extensively for ; use here. 4-20-04 W3CD ; ;**************************************************************************** process_pb btfsc PB_flags,PB_CS_FLG ; Check if in calibrate mode goto process_pb_exit ; If bit is set, skip processing ; Else, examine the button status 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 pressing of PB_2 goto process_both ; If PB_2 was pressed, then process both ; 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 ; Set the repeat flag to prevent last bsf PB_flags,PB_RPT_FLG ; update when PB_1 is released bcf PB_flags,PB_2_PRESS ; Clear the PB_2 flag, just in case goto pb_1_release_wait ; Loop some more pb_1_released ; Arrive here when PB1 was released movlw DECADE_INCREMENT ; Increase decade adjustment btfsc PB_flags,PB_2_PRESS ; Check if PB2 was pressed before repeating goto process_pb_exit ; If PB2 was pressed, skip update goto process_pb_release ; common code for pushbuttons ;****************************************************************************** ; ; Arrive here when PB_1 was not pressed. Check for PB_2 activity, which ; indicates that the displayed frequency has been accepted. If PB_1 is pressed ; during the PB_2 release wait, handle cursor right movement. ; ;****************************************************************************** pb_2_check btfsc PORTA,PB_2 ; Is PB_2 being pressed? goto process_pb_exit ; If not, then exit ; Else, process PB_2 pressing pb_2_release_wait call wait_32ms ; Wait a bit before checking again btfsc PORTA,PB_2 ; Is PB_2 still behing held? goto pb_2_released ; If it's released, then set flag btfsc PB_flags,PB_CS_FLG ; Check if sweep is active goto pb_2_release_wait ; If so, avoid checking PB 1 ; Else, not in sweep mode btfss PORTA,PB_1 ; Else, check for a late pressing of PB_1 goto process_both ; If PB_1 was pressed, then process both goto pb_2_release_wait ; Else, continue waiting. pb_2_released bsf PB_flags,F_SET ; Set the flag that freq was selected ; or end of sweep was requested. goto process_pb_exit ; Exit ;************************************************************************ ; ; Arrive here when both PB_1 and PB_2 were pressed. ; ;************************************************************************ process_both 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_PRESS ; Set this flag for PB1 handling 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 pb_both_done ; Then done btfsc PORTA,PB_2 ; Check PB_2 for same condition goto pb_both_done ; Done if operator released it ; Else, both buttons are still held decf PB_wait_count,f ; Decrement wait period btfss STATUS,Z ; Check if delay is done. goto both_release_wait ; If not, loop ; Else, timeout period expired. 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 ; Indicate that auto-repeat was active goto both_release_wait ; Loop some more pb_both_done ; Either PB_1 or PB_2, or both, 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 decade adjustment value. ; ;************************************************************************ process_pb_release btfsc PB_flags,PB_RPT_FLG ; If repeat flag was set, goto process_pb_exit ; don't do the final adjustment. ; call update_decade ; Else, adjust decade of interest btfss PORTA,PB_1 ; Test if PB_1 is still held goto process_pb ; If so, loop, don't exit ; Else, we're really done process_pb_exit ; Exit point bcf PB_flags,PB_2_PRESS ; Clear PB2 flag, since PB1 is released bcf PB_flags,PB_RPT_FLG ; Clear the common repeat flag and return ; Return to caller ;**************************************************************************** ; ; 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 1.0 Copied from PICELGen 2.0: 4-20-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 1.0 Copied from PICELGen 2.0 4-20-04 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 1.0: Copied from PICELGen 2.0 and replaced code that added ; fstep to frequency with a call to add_step. This saves ; program space. 4-22-04 W3CD. ; ; ***************************************************************************** calibrate bsf PB_flags,PB_CS_FLG ; Set the cal bit to prevent cursor movement movlw start_freq_0 ; Get pointer to working frequency registers movwf FSR ; Save for indiect addressing movlw EEcal_freq ; Point to calibration frequency in EEPROM call read_EEPROM_freq ; Read the freq (10.000000 MHz) movlw start_freq_0 ; Get pointer to the working freq registers movwf freq_pointer ; Store it for use ahead call show_freq ; The starting reference oscillator values have ; already been read from EEPROM. Convert reference ; freq and display it. movlw CMD_MOV_CUR_DISP+12 ; Short jump to LCD position 13 call cmnd2LCD ; Position the cursor movlw cal_msg ; Display "CAL" on LCD call display_message ; (position 16 is blank) ; 16 Character LCDs cal_loop call update_dds ; Calc new data and update the DDS chip call poll_encoder ; Wait until the encoder has moved. btfsc PORTA,PB_1 ; Check if PB1 was pressed goto cal_done ; If released, then exit calibrate mode ; Else, stay in cal and update osc. clrf fstep_3 ; Clear the three most significant clrf fstep_2 ; bytes of fstep clrf fstep_1 ; ; Now determine which increment to use movlw SMALL_FSTEP ; Assume that we are adjusting slowly btfss ren_read,PB_2 ; Check if PB2 was pressed movlw LARGE_FSTEP ; If so, then use the large increment ; movwf fstep_0 ; Update fstep_0 btfsc last_dir,DIR_BIT ; Are we moving down? goto faster ; No, increase the osc value ; Else, decreasing the osc value call invert_fstep ; Use this handy function to complement fstep ; and continue faster movlw osc_0 ; Point to frequency operand movwf freq_pointer ; Store in pointer call add_step ; osc_3..0 = osc_3..0 + f_step3..0 movlw start_freq_0 ; Restore pointer to the movwf freq_pointer ; working freq registers goto cal_loop ; Back to top of loop cal_done ; Arrive here when PB1 was released movlw osc_0 ; Get starting address of osc values movwf FSR ; Store in pointer clrw ; Point to start of EEPROM. call write_EEPROM_freq ; Write contents to EEPROM. bcf PB_flags,PB_CS_FLG ; Clear the calibrate flag return ; Return to the caller ;***************************************************************************** ; ; Function: calc_dds_word ; ; 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. ; ; Revision: ; Ver 1.0: Copied from PICELGen 2.0 4-20-04 W3CD ; ; ***************************************************************************** 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 count ; Keep running count movf osc_0,w ; Move the four osc bytes movwf temp_freq_0 ; to temporary storage for this multiply movf osc_1,w ; (Don't disturb original osc bytes) movwf temp_freq_1 ; movf osc_2,w ; movwf temp_freq_2 ; movf osc_3,w ; movwf temp_freq_3 ; ; Now develop the active vfo frequency pointer movf freq_pointer,w ; Get pointer to X_freq_0 mult_loop movwf FSR ; Store X_freq_0 pointer in FSR for indirect addr. bcf STATUS,C ; Start with Carry clear btfss temp_freq_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 X_freq_0 term addwf AD9850_1,f ; and add it in to total btfss STATUS,C ; Does this addition result in a carry? goto add7 ; No, continue with next freq term incfsz AD9850_2,f ; Yes, add one and check for another carry goto add7 ; No, continue with next freq term incfsz AD9850_3,f ; Yes, add one and check for another carry goto add7 ; No, continue with next freq term incf AD9850_4,f ; Yes, add one and continue add7 incf FSR,f ; Point to X_freq_1 movf INDF,w ; Use the X_freq_1 term addwf AD9850_2,f ; Add freq term to total in correct position btfss STATUS,C ; Does this addition result in a carry? goto add8 ; No, continue with next freq term incfsz AD9850_3,f ; Yes, add one and check for another carry goto add8 ; No, continue with next freq term incf AD9850_4,f ; Yes, add one and continue add8 incf FSR,f ; Point to X_freq_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 X_freq_3 movf INDF,w ; Use the X_freq_3 term addwf AD9850_4,f ; Add freq term to total in correct position noAdd rrf AD9850_4,f ; Shift next multiplier bit into position rrf AD9850_3,f ; Rotate bits to right from byte to byte rrf AD9850_2,f ; rrf AD9850_1,f ; rrf AD9850_0,f ; rrf temp_freq_3,f ; Shift next multiplicand bit into position rrf temp_freq_2,f ; Rotate bits to right from byte to byte rrf temp_freq_1,f ; rrf temp_freq_0,f ; movf freq_pointer,w ; Get the pointer to X_freq_0 decfsz 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 (un-comment for AD9850) 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. ; ***************************************************************************** ; ; Function: update_dds, send_dds_word ; ; Purpose: This routine sends the AD9850 control word to the DDS chip ; using a serial data transfer. The first entry point, update_dds, ; calls calc_dds_word to determine the AD9850 data. The second ; call is used when the DDS data is already calculated. ; ; Input: update_dds: freq_pointer points to the frequency value to be ; converted to DDS data before sending to DDS. ; ; send_dds_word: AD9850_4 ... AD9850_0 contain the data to send. ; ; Output: The DDS chip register is updated. ; ; Revsisions: ; Rev 1.0: Copied from PICELGen 2.0 and added call to calc_dds_word ; to reduce program space, since this happens quite often. ; 4-21-04 W3CD. ; ; ***************************************************************************** update_dds call calc_dds_word ; calculate the data to send to AD9850 send_dds_word movlw AD9850_0 ; Point FSR at AD9850 movwf FSR ; next_byte movf INDF,w ; movwf byte2send ; movlw 0x08 ; Set counter to 8 movwf bit_count ; next_bit rrf byte2send,f ; Test if next bit is 1 or 0 btfss STATUS,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 ; return ; Back to caller ; ; ***************************************************************************** ; * * ; * Purpose: Power on initialization of Liquid Crystal Display. The LCD * ; * controller chip must be equivalent to an Hitachi 44780. The * ; * LCD is assumed to be a 8x1 or a 16x1 display. 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_on_msg ; Offset to Sign-on message call display_message ; Display it on LCD movlw ver_msg ; 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: ; Rev 1.0: Copied from PICELGen 2.0 4-20-04 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. 1.0: Copied from PICELGen 2.0 and modified to use any ; frequency variable 4-21-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 freq_pointer,w ; Get X_freq_0 address movwf FSR ; Copy address to FSR movf INDF,w ; Get low byte of frequency movwf temp_freq_0 ; store it for conversion incf FSR,f ; Point to X_freq_1 movf INDF,w ; Get X_freq_1 movwf temp_freq_1 ; Store it incf FSR,f ; Point to X_freq_2 movf INDF,w ; Get the byte movwf temp_freq_2 ; Store it incf FSR,f ; Point to X_freq_3 movf INDF,w ; Get it movwf temp_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 temp_freq_0,f ; Rotate left, 0 -> LS bit, MS bit -> Carry rlf temp_freq_1,f ; Rotate left, Carry->LS bit, MS bit->Carry rlf temp_freq_2,f ; Rotate left, Carry->LS bit, MS bit->Carry rlf temp_freq_3,f ; Rotate left, Carry->LS bit, MS bit->Carry btfsc STATUS,C ; Is Carry clear? If so, skip next instruction bsf temp_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 ; ***************************************************************************** ; ; Function: show_freq ; ; Purpose: Display the frequency setting on the LCD. ; If a 1x8 LCD display so display freq in Hz - e.g. 14025000 ; If a 1x16 LCD display so display freq kHz - e.g 14,025.000 kHz ; ; Input: The values in BCD_4 ... BCD_0 ; ; Output: The number displayed on the LCD ; ; Revisions: ; Rev 1.0 Copied from PICELGen 2.0 and modified to reduce program ; space by substituting repetitive operations with calls ; to send_digit. 4-21-04 W3CD ; ; ***************************************************************************** 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 movlw ' ' ; Send a space call data2LCD ; to position 1 of LCD ; There are 8 digits to send, and these are ; packed into BCD_3 thru BCD_0. swapf BCD_3,w ; Put 10MHz BCD digit into lower nybble of W call send_digit ; Send the 10 MHz digit movf BCD_3,w ; Put 1MHz BCD digit into lower nybble of W call send_digit ; Send the 1MHz digit movlw ',' ; Get a comma call data2LCD ; Send it to LCD swapf BCD_2,w ; Put 100KHz BCD digit into lower nybble of W call send_digit ; Send that digit to LCD movf BCD_2,w ; Put 10KHz BCD digit into lower nybble of W call send_digit ; Send that digit to LCD swapf BCD_1,w ; Put 1KHz BCD digit into lower nybble of W call send_digit ; Send byte in W to LCD movlw '.' ; Get a period call data2LCD ; Send it to LCD movf BCD_1,w ; Put 100 Hz BCD digit into lower nybble of W call send_digit ; Send it to LCD swapf BCD_0,w ; Get 10 Hz BCD digit into lower nybble of W call send_digit ; Send it to LCD movf BCD_0,w ; Put 1 Hz BCD digit into lower nybble of W call send_digit ; Send to LCD movlw khz_msg ; Display "KHz" on LCD call display_message ; Position 16 is blank call set_cursor_pos ; Indicate the lowest digit position ; being modified. - W3CD 3-26-04 return ; ; ***************************************************************************** ; ; Function: send_digit ; ; Purpose: Formats a BCD digit into ASCII and sends it to the LCD. ; This is a support function for show_freq. ; ; Input: The low nybble of W contains the BCD digit to send ; ; Output: The formatted digit is send to the LCD ; ; Revisions: ; ; Rev 1.0 Added function to reduce program size 4-21-04 W3CD. ; ; ***************************************************************************** send_digit andlw LOW_NYBBLE_MASK ; Mask off high nybble addlw ASCII_NUM_BASE ; Add offset for ASCII char set (0x30) goto data2LCD ; Send byte in W to LCD and return to caller ; through data2LCD to save stack space ; ***************************************************************************** ; ; 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 1.0 Copied from PICELGen 2.01. 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. movlw CMD_MOV_CUR_DISP ; Cursor position base command iorwf cur_pos,w ; Or in the cursor postion call cmnd2LCD ; Move the cursor return ; ; ***************************************************************************** ; ; Function: clear_lcd ; ; Purpose: Clears the LCD and homes the cursor. ; ; Input: None ; ; Output: None ; ; Revisions: ; Ver 1.0: Copied from PICELGen 2.0 4-20-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 ; ***************************************************************************** ; ; Function: read_sweep_freqs ; ; Purpose: Reads the start, end, step and marker frequencies stored ; in EEPROM ; ; Input: None ; ; Output: start_freq_3..0, end_freq_3..0, step_freq_3..0 and ; marker_freq_3..0 are initialized with EEPROM data ; freq_pointer is set to start_freq_0 ; ; Revisions: ; Ver 1.0: New function 4-22-04 W3CD. ; ; ***************************************************************************** read_sweep_freqs movlw start_freq_0 ; Get pointer to starting frequency movwf freq_pointer ; Initial frequency data. movwf FSR ; Save for indiect addressing movlw EEstart_freq ; Point to starting freq in EEPROM call read_EEPROM_freq ; Read the freq movlw end_freq_0 ; Get pointer to ending frequency movwf FSR ; Save for indiect addressing movlw EEend_freq ; Point to ending freq in EEPROM call read_EEPROM_freq ; Read the freq movlw step_freq_0 ; Get pointer to frequency increment movwf FSR ; Save for indiect addressing movlw EEstep_freq ; Point to step freq in EEPROM call read_EEPROM_freq ; Read the freq movlw marker_freq_0 ; Get pointer to marker frequency movwf FSR ; Save for indiect addressing movlw EEmarker_freq ; Point to marker freq in EEPROM call read_EEPROM_freq ; Read the freq return ; ***************************************************************************** ; ; Function: write_sweep_freqs ; ; Purpose: Stores start_freq_3..0, end_freq_3..0, step_freq_3..0 and ; marker_freq_3..0 into EEPROM locations EEstart_loc, ; EEend_loc, EEstep_loc and EEmarker_loc, respectively. ; ; Input: None ; ; Output: EEPROM is updated ; ; Revisions: ; Ver 1.0: New function 4-22-04 W3CD. ; ; ***************************************************************************** write_sweep_freqs movlw start_freq_0 ; Get pointer to starting frequency movwf FSR ; Save for indiect addressing movlw EEstart_freq ; Point to starting freq in EEPROM call write_EEPROM_freq ; Store the freq movlw end_freq_0 ; Get pointer to ending frequency movwf FSR ; Save for indiect addressing movlw EEend_freq ; Point to ending freq in EEPROM call write_EEPROM_freq ; Store the freq movlw step_freq_0 ; Get pointer to frequency increment movwf FSR ; Save for indiect addressing movlw EEstep_freq ; Point to step freq in EEPROM call write_EEPROM_freq ; Store the freq movlw marker_freq_0 ; Get pointer to marker frequency movwf FSR ; Save for indiect addressing movlw EEmarker_freq ; Point to marker freq in EEPROM call write_EEPROM_freq ; Store the freq return ; ***************************************************************************** ; ; 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 ; w points to LSB of EEPROM frequency data entry ; ; Output: The 4-byte target is updated with EEPROM frequency ; ; Revisions: ; Ver 1.0: Copied from PICELGen 2.0 4-20-04 by W3CD. ; Ver 1.1: Adapted to PIC16F628A. 10-04-05 W3CD ; ; ***************************************************************************** read_EEPROM_freq movwf ee_addr ; Save the address movlw FREQ_COUNT ; Get the byte count movwf count ; into loop counter read_EEPROM_loop call read_EEPROM ; Read a byte 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 ; **************************************************************************** ; ; Function: write_EEPROM_freq ; ; Purpose: This function saves a 4-byte frequency value in EEPROM. ; ; Input: FSR points to the LSB of the frequency data to store ; w points to LSB of EEPROM frequency data entry ; ; Output: none ; ; Revisions: ; Ver 1.0: New function. 4-22-04 W3CD ; Ver 1.1: Adapted to PIC16F628A. 10-04-05 W3CD ; ; **************************************************************************** write_EEPROM_freq movwf ee_addr ; Save the write address movlw FREQ_COUNT ; Prepare loop to write contents movwf count ; of X_freq_0..3 into EEPROM write_EE_loop movf INDF,w ; Get the data to be written call write_EEPROM ; Do the write thing incf FSR,f ; Bump freq pointer incf ee_addr,f ; Increment EEPROM write address decfsz count,f ; Decrement byte count goto write_EE_loop ; Iterate while bytes remain to be written ; Fall through when done return ; Done! ; ***************************************************************************** ; * * ; * Purpose: Write the byte of data at EEDATA to the EEPROM at address * ; * EEADR. * ; * * ; * Input: ee_addr contains the EEPROM write address * ; * w contains the write data * ; * * ; * Output: The EEPROM location is updated. * ; * * ; * Revisions: * ; * Ver 1.1: Adapted to PIC16F628A. 10-04-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. * ; * * ; ***************************************************************************** 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 ; ***************************************************************************** ; * * ; * 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