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