; **************************************************************************** ; * Signal Generator (VFO) with Direct Digital Synthesis * ; * Version 3a * ; * September 18, 2003 * ; * * ; **************************************************************************** ; Description: ; This is the control program for a DDS VFO built with an AD9850 DDS chip, a ; shaft encoder, a push button switch and an Liquid crystal display. ; ; Features: ; VARIABLE RATE TUNING based on the speed at which the encoder is turned. The ; encoder also has a built in switch which will change the step size from 1Hz ; to 1kHz if the encoder shaft is pressed down while turning. ; ; BAND MEMORIES an external push button switch allows the frequency to be ; cycled around the HF ham bands. ; ; CALIBRATE MODE is entered if the external push button is pressed during ; power on. The display is set to 10,000.000 CAL and remains fixed, even as ; adjustments are being made. If the push button is held pressed, then turning ; the shaft encoder will increase or decrease the value "osc" used to ; calculate the DDS control word. The basic calibrate adjustment rate is very ; low (on the order of a few cycles per turn of the encoder). A somewhat ; faster adjustment speed is available by pressing the encoder shaft down ; while turning. An external frequency counter on the DDS output is required ; to observe this adjustment. To exit calibrate mode, release the external ; push button and turn the shaft encoder one more time. The calibrated value ; of "osc" will then be stored in EEPROM memory. ; ;****************************************************************************** ; Author - Curtis W. Preuss - WB2V ; ; Modification History ; 8/19/98 - Version 1 - Initial Version by Curtis W. Preuss - WB2V ; 12/xx/98 - Version 2 - Converted to MPASM by Bruce Stough, AA0ED ; 4/21/99 - Version 3 - Fixed and modified by ; Bruce Stough, AA0ED (sbs1@visi.com) and ; Craig Johnson, AA0ZZ (aa0zz@arrl.net) ; ; FIXES: ; 1) Fix a bug which caused the frequency to jump to ; the maximum when going towards zero. ; 2) Fix several SMASM to MPASM translation bugs. ; - Code worked, but several cases of "hard coded" ; constants remained that should have been ; changed to labels to allow data tables to ; be moved and/or modified. This bug could ; cause a reference to a wrong variable. ; - PortB vs TRISB causing confusion. ; MODIFICATIONS/ADDITIONS: ; 1) The lower frequency is changed from 1Khz to zero. ; 2) Added band table entries of 0 Hz and 30 MHz. ; 3) Changed wait routine names for clarity. ; 4) Added comments throughout. ; 5) Subroutine headers added. ; - Inputs and Outputs specified ; 6) Changed some data labels for clarity. ; 7) Changed some routines for efficiency. ; - Improve path length and save memory ; NOTE: OUR GOAL was to make this code clear and ; easy to understand so that it can be used as ; a springboard for additional changes. ; By documenting subroutines as clearly as ; possible, we hope readers will be able to ; easily use the subroutines in other projects. ; 9/17/03 Corrected error in PIC16F84 diagram. Pin 4 should be ; connected to +5v instead of ground. ; ;***************************************************************************** ; ; Target Controller - PIC16F84 ; __________ ; ENCODER SWITCH--RA2 |1 18| RA1---------ENCODER A ; PB SWITCH-------RA3 |2 17| RA0---------ENCODER B ; +5V-------------RA4 |3 16| OSC1--------XTAL ; +5V-----------!MCLR |4 15| OSC2--------XTAL ; Ground----------Vss |5 14| VDD---------+5 V ; DDS LOAD--------RB0 |6 13| RB7---------DDS DATA/LCD 14 ; LCD_rs----------RB1 |7 12| RB6---------LCD 13 ; LCD_rw----------RB2 |8 11| RB5---------DDS CLOCK/LCD 12 ; LCD_e-----------RB3 |9 10| RB4---------LCD 11 ; ---------- ; ;***************************************************************************** ; ; ********** HARDWARE CHANGES NEEDED TO FAR CIRCUITS BOARD ********** ; ; The circuit board available from FAR Circuits is based on the original ; QEX article (7/97) by Curt, WB2V, and works with the original dds_vfo ; code. Subsequently, Curt modified the code to add the calibrate ; and band select functions. ; ; This enhanced PIC code requires a hardware change to allow for the push ; button which is used to select the calibrate mode and to do band ; selection. The change removes the +5 volt connection to pin 2 of ; the PIC chip, and connects a 10K ohm pull-up resistor to pin 2. The ; push button is connected between pin 2 and ground, so that the pin is ; grounded when the button is pressed and held high when the button is ; released. In addition, instead of using the output of Pin 2 to power ; the LCD and control the LCD contrast, the LCD is connected directly to ; +5 volts. ; ; The change can be done as follows: ; ; 1. Locate the PC trace which connects pin 2 of the PIC chip to the ; LCD contrast control pot. This was originally used to power the ; LCD by setting bit 3 of Port A (pin 2) high. Drill a small hole ; (#60 - .040") through this trace as near as possible (1/16") to ; pin 2 of the PIC socket. ; ; 2. Near pin 2 of the PIC socket, cut the trace about 1/8" beyond the ; new hole. Using a 1/4" drill, remove the foil around the hole ; on the ground plane side of the board to avoid shorting pin 2 to ; ground when the resistor gets inserted in the hole (Step 4). ; ; 3. Locate the +5 volt trace that passes between pin 1 and pin 2 of the ; PIC socket. Drill a new hole through this trace in the place where ; it is close to pin 1. Remove the ground side foil from around this ; hole with a 1/4" drill to avoid shorting +5 v to ground when the ; resistor lead is inserted. ; ; 4. Install a 10K resistor in the new PC board holes. Mount it vertically, ; with the short resistor lead in the +5 v trace hole and the other end ; bent close to the resistor body and down into the hole near pin 2 of ; the PIC socket. Make the solder connection to the traces only, not to ; ground plane foil on the other side of the board. Make sure both sides ; of the +5 v trace are soldered to the resistor lead. ; ; 5. Connect a wire from one side of the push button to the pin 2 side of ; the new 10K resistor. This can be done by connecting the wire to the ; top of the vertically mounted resistor. Connect another wire from the ; other side of the push button to ground. ; ; 6. Using a bit of solid wire, connect the pin of the LCD contrast ; pot that used to be connected to pin 2 of the PIC socket to the ; +5 volt trace. This now supplies power to the LCD. ; ; **************************************************************************** ; * Device type and options. * ; **************************************************************************** ; processor PIC16F84 radix dec ; ; **************************************************************************** ; * Configuration fuse information: * ; **************************************************************************** _CP_ON EQU H'000F' _CP_OFF EQU H'3FFF' _PWRTE_ON EQU H'3FF7' _PWRTE_OFF EQU H'3FFF' _WDT_ON EQU H'3FFF' _WDT_OFF EQU H'3FFB' _LP_OSC EQU H'3FFC' _XT_OSC EQU H'3FFD' _HS_OSC EQU H'3FFE' _RC_OSC EQU H'3FFF' ; __config _CP_OFF & _PWRTE_ON & _WDT_ON & _XT_OSC ; ; **************************************************************************** ; * General equates. These may be changed to accommodate the reference clock* ; * frequency, the desired upper frequency limit, and the default startup * ; * frequency. * ; **************************************************************************** ; ; ref_osc represents the change in the frequency control word which results ; in a 1 Hz change in output frequency. It is interpreted as a fixed point ; integer in the format . ; ; The values for common oscillator frequencies are as follows: ; ; Frequency ref_osc_3 ref_osc_2 ref_osc_1 ref_osc_0 ; ; 120.00 MHz 0x23 0xCA 0x98 0xCE ; 100.00 MHz 0x2A 0xF3 0x1D 0xC4 ; 90.70 MHz 0x2F 0x5A 0x82 0x7A ; 66.66 MHz 0x40 0x6E 0x52 0xE7 ; 66.00 MHz 0x41 0x13 0x44 0x5F ; 50.00 MHz 0x55 0xE6 0x3B 0x88 ; ; To calculate other values: ; ref_osc_3 = (2^32 / oscillator_freq_in_Hertz). ; ref_osc_2, ref_osc_1, and ref_osc_0 are the fractional part of ; (2^32 / oscillator_freq_in_Hertz) times 2^24. ; Note: 2^32 = 4294967296 and 2^24 = 16777216 ; ; For example, for a 120 MHz clock: ; ref_osc_3 is (2^32 / 120 x 10^6) = 35.791394133 truncated to 35 (0x23) ; ref_osc_2 is the high byte of (.791394133 x 2^24) = 13277390.32 ; 13277390.32 = 0xCA98CE, so high byte is CA. ; ref_osc_1 is the next byte of 0xCA98CE, or 98 ; ref_osc_0 is the last byte of 0xCA98CE, or CE ; ;==== 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 ; ; **************************************************************************** ; * Port and EEPROM Constants * ; **************************************************************************** ; PortA equ 0x05 PortB equ 0x06 TRISA equ 0x05 TRISB equ 0x06 EEdata equ 0x08 EEadr equ 0x09 WREN equ 0x02 WR equ 0x01 RD equ 0x00 ; ; **************************************************************************** ; * ID location information: * ; * (MPASM warns about DW here, don't worry) * ; **************************************************************************** ; ORG 0x2000 DATA 0x007F DATA 0x007F DATA 0x007F DATA 0x007F ; ; ; **************************************************************************** ; * Setup the initial constant, based on the frequency of the reference * ; * oscillator. This can be tweaked with the calibrate function. * ; **************************************************************************** ; ORG 0x2100 DATA ref_osc_0 DATA ref_osc_1 DATA ref_osc_2 DATA ref_osc_3 ; ; Clear unused EEPROM bytes. ; DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; ; **************************************************************************** ; * RAM page independent file registers: * ; **************************************************************************** ; INDF EQU 0x00 PCL EQU 0x02 STATUS EQU 0x03 FSR EQU 0x04 PCLATH EQU 0x0A INTCON EQU 0x0B ; ; ***************************************************************************** ; * Bit numbers for the STATUS file register: * ; ***************************************************************************** ; B_RP0 EQU 5 B_NTO EQU 4 B_NPD EQU 3 B_Z EQU 2 B_DC EQU 1 B_C EQU 0 ; ; **************************************************************************** ; * Assign names to IO pins. * ; **************************************************************************** ; ; B register bits: ; DDS_load equ 0x00 ; Update pin on AD9850 LCD_rs equ 0x01 ; 0=instruction, 1=data LCD_rw equ 0x02 ; 0=write, 1=read LCD_e equ 0x03 ; 0=disable, 1=enable DDS_clk equ 0x05 ; AD9850 write clock DDS_dat equ 0x07 ; AD9850 serial data input ; ; A register bits: ; pb_switch equ 0x03 ; Calibrate Push Button, (active low) ; ; **************************************************************************** ; * 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 ; 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 bsf 0x01,7 ; Disable weak pullups movlw 0xFF ; Tristate port A movwf TRISA ; clrf TRISB ; Set port B to all outputs bcf STATUS,B_RP0 ; Switch back to bank 0 call init_LCD ; Initialize the LCD ; ; Enter Calibrate Mode if push button is pressed while turning the ; power on. ; btfsc PortA,pb_switch ; Is the switch pressed? goto read_EEocs ; No, get clock freq from EEPROM call calibrate ; Yes, calibrate ; ; Get the reference oscillator constant from the EEPROM. ; read_EEocs clrf EEadr ; Reset the EEPROM read address call read_EEPROM ; Read EEPROM movf EEdata,w ; Get the first osc byte movwf osc_0 ; Save osc frequency call read_EEPROM ; Get next byte movf EEdata,w ; movwf osc_1 ; Save it call read_EEPROM ; Get the third byte movf EEdata,w ; movwf osc_2 ; Save it call read_EEPROM ; Get the fourth byte movf EEdata,w ; movwf osc_3 ; Save it ; ; Set the power on frequency to the defined value. ; movlw default_0 ; Get the least significant byte movwf freq_0 ; Save it movlw default_1 ; Get the next byte movwf freq_1 ; Save it movlw default_2 ; And the next movwf freq_2 ; Save it movlw default_3 ; Get the most significant byte movwf freq_3 ; Save it ; ; 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 ; ; Get the power on encoder value. ; movf PortA,w ; Read port A movwf ren_read ; Save it in ren_read movlw 0x03 ; Get encoder mask andwf ren_read,w ; Get encoder bits movwf ren_old ; Save in ren_old ; ; Initialize 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 push button switch was not pressed, then the variable * ; * fstep is calculated based on the delay between shaft encoder * ; * changes. ren_timer contains the delay value determined by * ; * the poll_encoder subroutine. The variable fstep is added or * ; * subtracted from the current VFO frequency stored in freq. * ; * The contents of freq are then converted to a BCD number in * ; * subroutine bin2BCD. The subroutine show_freq is then called * ; * to display the result on the Liquid Crystal Display. Next, the * ; * subroutine calc_dds_word is used to calculate the DDS * ; * frequency control word from the values in freq and osc. * ; * The result is stored in AD9850. This data is transferred to * ; * the AD9850 DDS chip by calling the subroutine send_dds_word. * ; * * ; * If the push button is pressed while turning the encoder then * ; * freq is loaded with a constant stored in band_table. The * ; * variable band is used as an index into the table. Band * ; * is incremented or decremented based on the encoder direction. * ; * * ; * Input: None. * ; * * ; * Output: None. * ; * * ; ***************************************************************************** ; main call poll_encoder ; Check for knob movement btfss ren_read,3 ; Change band? goto change_band ; Yes, change band ; ; Determine step size to use (1 Hz or 1 kHz). ; clrf fstep_3 ; Guess that we want 1 Hz steps by clrf fstep_2 ; setting fstep to one. clrf fstep_1 ; movlw 0x01 ; movwf fstep_0 ; btfsc ren_read,2 ; Is the encoder switch pressed? goto go_step ; No, use the 1 Hz step movlw 0xE8 ; Yes, set the step value to 1 kHz movwf fstep_0 ; by setting fstep_0 to 0xE8 and movlw 0x03 ; fstep_1 to 0x03 movwf fstep_1 ; goto go_step ; Use the 1 kHz step ; ; Adjust the tuning step based on ren_timer. ren_timer is incremented ; by 8 from its initial value of 0x0040 each time the poll_encoder finds ; no change in the encoder input, until the high bit of ren_timer_1 ; becomes a one. The default fstep of 1 Hz is multiplied by two for ; each leading zero in ren_timer, up to a maximum of 9 times. (This is ; because ren_timer starts at 0x0040, only the first nine bits can be ; zero in a row). The faster the knob is turned, the lower the number ; in ren_timer will be, and the larger the step value will be. ; ; bump_step bcf STATUS,B_C ; Clear the carry flag rlf fstep_0,f ; Multiply the step by 2 by rotating left rlf fstep_1,f ; rlf fstep_2,f ; rlf fstep_3,f ; go_step rlf ren_timer_0,f ; Multiply the encoder timer by 2 rlf ren_timer_1,f ; btfss STATUS,B_C ; Has a one floated to the carry yet? goto bump_step ; No, then double the step size ; ; 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 write ; Update LCD and DDS up call add_step ; Add fstep to freq call check_add ; Make sure we did not exceed the maximum goto write ; Update LCD and DDS ; ; ***************************************************************************** ; * * ; * 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 goto main ; Continue polling the encoder (endless loop) ; ; ***************************************************************************** ; * * ; * Purpose: This routine reads the frequency value of a band table entry * ; * pointed to by band and returns it in freq_3...freq_0. * ; * * ; * Input: band must contain the index of the desired band entry * 4 * ; * (with the entries numbered from zero). * ; * * ; * Output: The band frequency in freq. * ; * * ; ***************************************************************************** ; get_band movf band,w ; Get the index of the high byte call band_table ; Get the value into W movwf freq_3 ; Save it in freq_3 incf band,f ; Increment index to next byte movf band,w ; Get the index of the next byte call band_table ; Get the value into W movwf freq_2 ; Save it in freq_2 incf band,f ; Increment index to the next byte movf band,w ; Get the index to the next byte call band_table ; Get the value into W movwf freq_1 ; Save it in freq_1 incf band,f ; Increment index to the low byte movf band,w ; Get the index to the low byte call band_table ; Get the value into W movwf freq_0 ; Save it in freq_0 movlw 0x03 ; Get a constant three subwf band,f ; Restore original value of band return ; Return to the caller ; ; ***************************************************************************** ; * * ; * Purpose: Power on initialization of Liquid Crystal Display. The LCD * ; * controller chip must be equivalent to an Hitachi 44780. The * ; * LCD is assumed to be a 16 X 1 display. * ; * * ; * Input: None * ; * * ; * Output: None * ; * * ; ***************************************************************************** ; init_LCD call wait_64ms ; Wait for LCD to power up movlw 0x30 ; LCD init instruction (First) movwf PortB ; Send to LCD via RB7..RB0 bsf PortB,LCD_e ; Set the LCD E line high, call wait_64ms ; wait a "long" time, bcf PortB,LCD_e ; and then Clear E movlw 0x30 ; LCD init instruction (Second) movwf PortB ; Send to LCD via RB7..RB0 bsf PortB,LCD_e ; Set E high, call wait_32ms ; wait a while, bcf PortB,LCD_e ; and then Clear E movlw 0x30 ; LCD init instruction (Third) movwf PortB ; Send to LCD via RB7..RB0 bsf PortB,LCD_e ; Set E high, call wait_32ms ; wait a while, bcf PortB,LCD_e ; and then Clear E movlw 0x20 ; 4-bit mode instruction movwf PortB ; Send to LCD via RB7..RB0 bsf PortB,LCD_e ; Set E high, call wait_16ms ; wait a while, bcf PortB,LCD_e ; and then Clear E movlw 0x28 ; 1/16 duty cycle, 5x8 matrix call cmnd2LCD ; Send command in w to LCD movlw 0x08 ; Display off, cursor and blink off call cmnd2LCD ; Send command to LCD movlw 0x01 ; Clear and reset cursor call cmnd2LCD ; Send command in w to LCD movlw 0x06 ; Set cursor to move right, no shift call cmnd2LCD ; Send command in w to LCD movlw 0x0C ; Display on, cursor and blink off call cmnd2LCD ; Send command in w to LCD return ; ; ; ***************************************************************************** ; * * ; * Purpose: This routine adds the 32 bit value of fstep to the 32 bit * ; * value in freq. When incrementing, the fstep value is a * ; * positive integer. When decrementing, fstep is the complement * ; * of the value being subtracted. * ; * * ; * Input: The 32 bit values in fstep and freq * ; * * ; * Output: The sum of fstep and freq is stored in freq. When incrementing * ; * this value may exceed the maximum. When decrementing, it may * ; * go negative. * ; * * ; ***************************************************************************** add_step movf fstep_0,w ; Get low byte of the increment addwf freq_0,f ; Add it to the low byte of freq btfss STATUS,B_C ; Any carry? goto add1 ; No, add next byte incfsz freq_1,f ; Ripple carry up to the next byte goto add1 ; No new carry, add next byte incfsz freq_2,f ; Ripple carry up to the next byte goto add1 ; No new carry, add next byte incf freq_3,f ; Ripple carry up to the highest byte add1 movf fstep_1,w ; Get the next increment byte addwf freq_1,f ; Add it to the next higher byte btfss STATUS,B_C ; Any carry? goto add2 ; No, add next byte incfsz freq_2,f ; Ripple carry up to the next byte goto add2 ; No new carry, add next byte incf freq_3,f ; Ripple carry up to the highest byte add2 movf fstep_2,w ; Get the next to most significant increment addwf freq_2,f ; Add it to the freq byte btfss STATUS,B_C ; Any carry? goto add3 ; No, add last byte incf freq_3,f ; Ripple carry up to the highest byte add3 movf fstep_3,w ; Get the most significant increment byte addwf freq_3,f ; Add it to the most significant freq return ; Return to the caller ; ; ***************************************************************************** ; * * ; * Purpose: Check if freq exceeds the upper limit. * ; * * ; * Input: The 32 bit values in freq * ; * * ; * Output: If freq is below the limit, it is unchanged. Otherwise, it is * ; * set to equal the upper limit. * ; * * ; ***************************************************************************** ; check_add ; ; Check the most significant byte. ; movlw 0xFF-limit_3 ; Get (FF - limit of high byte) addwf freq_3,w ; Add it to the current high byte btfsc STATUS,B_C ; Was high byte too large? goto set_max ; Yes, apply limit movlw limit_3 ; Get high limit value subwf freq_3,w ; Subtract the limit value btfss STATUS,B_C ; Are we at the limit for the byte? goto exit1 ; No, below. Checks are done. ; ; Check the second most significant byte. ; movlw 0xFF-limit_2 ; Get (FF - limit of next byte) addwf freq_2,w ; Add it to the current byte btfsc STATUS,B_C ; Is the current value too high? goto set_max ; Yes, apply the limit movlw limit_2 ; Second limit byte subwf freq_2,w ; Subtract limit value btfss STATUS,B_C ; Are we at the limit for the byte? goto exit1 ; No, below. Checks are done. ; ; Check the third most significant byte. ; movlw 0xFF-limit_1 ; Get (FF - limit of next byte) addwf freq_1,w ; Add it to the current byte btfsc STATUS,B_C ; Is the current value too high? goto set_max ; Yes, apply the limit movlw limit_1 ; Third limit byte subwf freq_1,w ; Subtract limit value btfss STATUS,B_C ; Are we at the limit for the byte? goto exit1 ; No, below. Checks are done. ; ; Check the least significant byte. ; movlw limit_0 ; Fourth limit byte subwf freq_0,w ; Subtract limit value btfss STATUS,B_C ; Are we at the limit for the byte? goto exit1 ; No, below. Checks are done. set_max movlw limit_0 ; Get least significant limit movwf freq_0 ; Set it in freq movlw limit_1 ; Get the next byte limit movwf freq_1 ; Set it in freq_1 movlw limit_2 ; Get the next byte limit movwf freq_2 ; Set it in freq_2 movlw limit_3 ; Get the most significant limit movwf freq_3 ; Set it in freq_3 exit1 return ; Return to the caller ; ; ***************************************************************************** ; * * ; * Purpose: Subtract the increment step from freq, checking that it does * ; * not go below zero. * ; * * ; * Input: The values in fstep and freq. * ; * * ; * Output: The updated value in freq. * ; * * ; ***************************************************************************** ; sub_step comf fstep_0,f ; Subtraction of fstep from comf fstep_1,f ; freq is done by adding the comf fstep_2,f ; twos compliment of fstep to comf fstep_3,f ; freq. incfsz fstep_0,f ; Increment last byte goto comp_done ; Non-zero, continue incfsz fstep_1,f ; Increment next byte goto comp_done ; Non-zero, continue incfsz fstep_2,f ; Increment next byte goto comp_done ; Non-zero, continue incf fstep_3,f ; Increment the high byte comp_done call add_step ; Add the compliment to do the subtraction ; ; If the frequency has gone negative, clear it to zero. ; btfss freq_3,7 ; Is high order frequency byte "negative"? goto exit2 ; No, keep going set_min clrf freq_0 ; Yes, set the frequency to zero clrf freq_1 ; clrf freq_2 ; clrf freq_3 ; exit2 return ; Return to the caller ; ; ***************************************************************************** ; * * ; * Purpose: This routine does the following: * ; * 1. Records how long it took for the knob to move a notch * ; * in ren_timer. * ; * 2. Clears the watchdog timer. * ; * 3. Reads the encoder bits until a change is detected, then * ; * determines the direction the knob was moved. * ; * * ; * Input: Knob input read from port A * ; * ren_old -> the last encoder bits read * ; * last_dir -> the last direction moved * ; * * ; * Output: ren_timer -> an indication the speed of the knob. * ; * ren_new -> the current encoder bits * ; * last_dir -> the last direction (0 = down, 2 = up) * ; * * ; ***************************************************************************** ; poll_encoder clrf ren_timer_1 ; Put starting values in ren_timer movlw 0x40 ; Start with the high bit set in ren_timer movwf ren_timer_0 ; read_encoder clrwdt ; Reset the watchdog timer btfsc ren_timer_1,7 ; Has the bit floated to bottom of ren_timer? goto no_inc ; Yes, don't move it any further movlw 0x08 ; addwf ren_timer_0,f ; btfsc STATUS,B_C ; Did the add force a carry? incf ren_timer_1,f ; Yes, then add one to ren_timer_1 no_inc ; movf PortA,w ; Get the current encoder value movwf ren_read ; Save it movlw 0x03 ; Get encoder mask andwf ren_read,w ; Isolate encoder bits movwf ren_new ; Save new value xorwf ren_old,w ; Has it changed? btfsc STATUS,B_Z ; goto read_encoder ; No, keep looking until it changes ; ; Determine which direction the encoder turned. ; bcf STATUS,B_C ; Clear the carry bit rlf ren_old,f ; movf ren_new,w ; xorwf ren_old,f ; movf ren_old,w ; andlw 0x02 ; movwf next_dir ; xorwf last_dir,w ; ; ; Prevent encoder slip from giving a false change in direction. ; btfsc STATUS,B_Z ; Zero? goto pe_continue ; No slip; keep going movf next_dir,w ; Yes, update direction movwf last_dir ; movf ren_new,w ; Save the current encoder bits for next time movwf ren_old ; goto read_encoder ; Try again pe_continue btfsc ren_old,1 ; Are we going down? goto up2 ; No, indicate we are going up clrf last_dir ; Yes, clear last_dir goto exit3 ; Finish and return up2 movlw 0x02 ; Set UP value in last_dir movwf last_dir ; exit3 movf ren_new,w ; Get the current encoder bits movwf ren_old ; Save them in ren_old for the next time return ; Return to the caller ; ; ***************************************************************************** ; * * ; * Purpose: This routine is entered at start up if the push button is * ; * pressed. "10,000.00 CAL" is displayed on the LCD, and the * ; * the DDS chip is programmed to produce 10 MHz, based on the * ; * osc value stored in the EEPROM. As long as the button is * ; * pressed, the osc value is slowly altered to allow the output * ; * to be trimmed to exactly 10 MHz. Once the encoder is turned * ; * after the button is released, the new osc value is stored in * ; * the EEPROM and normal operation begins. * ; * * ; * Input: The original osc constant in EEPROM * ; * * ; * Output: The corrected osc constant in EEPROM * ; * * ; ***************************************************************************** ; calibrate movlw 0x80 ; Set frequency to 10MHz by movwf freq_0 ; setting freq to the binary equivalent movlw 0x96 ; of 10,000,000. movwf freq_1 ; . movlw 0x98 ; . movwf freq_2 ; . movlw 0x00 ; . movwf freq_3 ; . ; ; Read the starting reference oscillator value form EEPROM. ; clrf EEadr ; Reset the EEPROM read address call read_EEPROM ; Read EEPROM movf EEdata,w ; Get the first osc byte movwf osc_0 ; Save osc frequency call read_EEPROM ; Get next byte movf EEdata,w ; movwf osc_1 ; Save it call read_EEPROM ; Get the third byte movf EEdata,w ; movwf osc_2 ; Save it call read_EEPROM ; Get the fourth byte movf EEdata,w ; movwf osc_3 ; Save it call bin2BCD ; Calculate BCD version of 10,000.00 call show_freq ; Display the frequency on the LCD movlw 0xC4 ; Point LCD at digit 14 movwf LCD_char ; call cmnd2LCD ; movlw 'C' ; Send a C movwf LCD_char ; call data2LCD ; movlw 'A' ; Send an A movwf LCD_char ; call data2LCD ; movlw 'L' ; Send an L movwf LCD_char ; call data2LCD ; cal_loop call calc_dds_word ; Calculate DDS value based on current osc call send_dds_word ; Update the DDS chip call poll_encoder ; Wait until the encoder has moved. clrf fstep_3 ; Clear the three most significant clrf fstep_2 ; bytes of fstep clrf fstep_1 ; movlw 0x10 ; Assume that we are adjusting slowly movwf fstep_0 ; Use small increment btfsc ren_read,2 ; Was the encoder changing slowly? goto update_osc ; Yes, then continue with small increment movlw 0x80 ; No, then use the large increment movwf fstep_0 ; update_osc nop ; Wait a cycle btfsc last_dir,1 ; Are we moving down? goto faster ; No, increase the osc value ; ; slower ; comf fstep_0,f ; Subtraction of fstep is done by comf fstep_1,f ; adding the twos compliment of fsetp comf fstep_2,f ; to osc comf fstep_3,f ; incfsz fstep_0,f ; Increment last byte goto faster ; Non-zero, continue incfsz fstep_1,f ; Increment next byte goto faster ; Non-zero, continue incfsz fstep_2,f ; Increment next byte goto faster ; Non-zero, continue incf fstep_3,f ; Increment the high byte faster movf fstep_0,w ; Get the low byte increment addwf osc_0,f ; Add it to the low osc byte btfss STATUS,B_C ; Was there a carry? goto add4 ; No, add the next bytes incfsz osc_1,f ; Ripple carry up to the next byte goto add4 ; No new carry, add the next bytes incfsz osc_2,f ; Ripple carry up to the next byte goto add4 ; No new carry, add the next bytes incf osc_3,f ; Ripple carry up to the highest byte add4 movf fstep_1,w ; Get the second byte increment addwf osc_1,f ; Add it to the second osc byte btfss STATUS,B_C ; Was there a carry? goto add5 ; No, add the third bytes incfsz osc_2,f ; Ripple carry up to the next byte goto add5 ; No new carry, add the third bytes incf osc_3,f ; Ripple carry up to the highest byte add5 movf fstep_2,w ; Get the third byte increment addwf osc_2,f ; Add it to the third osc byte btfss STATUS,B_C ; Was there a carry? goto add6 ; No, add the fourth bytes incf osc_3,f ; Ripple carry up to the highest byte add6 movf fstep_3,w ; Get the fourth byte increment addwf osc_3,f ; Add it to the fourth byte btfss ren_read,3 ; Is the button still pressed? goto cal_loop ; Yes, stay in calibrate mode clrf EEadr ; Write final value to EEPROM movf osc_0,w ; Record the first movwf EEdata ; osc call write_EEPROM ; byte movf osc_1,w ; Record the second movwf EEdata ; osc call write_EEPROM ; byte movf osc_2,w ; Record the third movwf EEdata ; osc call write_EEPROM ; byte movf osc_3,w ; Record the fourth movwf EEdata ; osc call write_EEPROM ; byte return ; Return to the caller ; ; ***************************************************************************** ; * * ; * Purpose: Multiply the 32 bit number for oscillator frequency times the * ; * 32 bit number for the displayed frequency. * ; * * ; * * ; * Input: The reference oscillator value in osc_3 ... osc_0 and the * ; * current frequency stored in freq_3 ... freq_0. The reference * ; * oscillator value is treated as a fixed point real, with a 24 * ; * bit mantissa. * ; * * ; * Output: The result is stored in AD9850_3 ... AD9850_0. * ; * * ; ***************************************************************************** ; calc_dds_word clrf AD9850_0 ; Clear the AD9850 control word bytes clrf AD9850_1 ; clrf AD9850_2 ; clrf AD9850_3 ; clrf AD9850_4 ; movlw 0x20 ; Set count to 32 (4 osc bytes of 8 bits) movwf mult_count ; Keep running count movf osc_0,w ; Move the four osc bytes movwf osc_temp_0 ; to temporary storage for this multiply movf osc_1,w ; (Don't disturb original osc bytes) movwf osc_temp_1 ; movf osc_2,w ; movwf osc_temp_2 ; movf osc_3,w ; movwf osc_temp_3 ; mult_loop bcf STATUS,B_C ; Start with Carry clear btfss osc_temp_0,0 ; Is bit 0 (Least Significant bit) set? goto noAdd ; No, don't need to add freq term to total movf freq_0,w ; Yes, get the freq_0 term addwf AD9850_1,f ; and add it in to total btfss STATUS,B_C ; Does this addition result in a carry? goto add7 ; No, continue with next freq term incfsz AD9850_2,f ; Yes, add one and check for another carry goto add7 ; No, continue with next freq term incfsz AD9850_3,f ; Yes, add one and check for another carry goto add7 ; No, continue with next freq term incf AD9850_4,f ; Yes, add one and continue add7 movf freq_1,w ; Use the freq_1 term addwf AD9850_2,f ; Add freq term to total in correct position btfss STATUS,B_C ; Does this addition result in a carry? goto add8 ; No, continue with next freq term incfsz AD9850_3,f ; Yes, add one and check for another carry goto add8 ; No, continue with next freq term incf AD9850_4,f ; Yes, add one and continue add8 movf freq_2,w ; Use the freq_2 term addwf AD9850_3,f ; Add freq term to total in correct position btfss STATUS,B_C ; Does this addition result in a carry? goto add9 ; No, continue with next freq term incf AD9850_4,f ; Yes, add one and continue add9 movf freq_3,w ; Use the freq_3 term addwf AD9850_4,f ; Add freq term to total in correct position noAdd rrf AD9850_4,f ; Shift next multiplier bit into position rrf AD9850_3,f ; Rotate bits to right from byte to byte rrf AD9850_2,f ; rrf AD9850_1,f ; rrf AD9850_0,f ; rrf osc_temp_3,f ; Shift next multiplicand bit into position rrf osc_temp_2,f ; Rotate bits to right from byte to byte rrf osc_temp_1,f ; rrf osc_temp_0,f ; decfsz mult_count,f ; One more bit has been done. Are we done? goto mult_loop ; No, go back to use this bit clrf AD9850_4 ; Yes, clear _4. Answer is in bytes _3 .. _0 return ; Done. ; ; ***************************************************************************** ; * * ; * Purpose: This routine sends the AD9850 control word to the DDS chip * ; * using a serial data transfer. * ; * * ; * Input: AD9850_4 ... AD9850_0 * ; * * ; * Output: The DDS chip register is updated. * ; * * ; ***************************************************************************** ; send_dds_word movlw AD9850_0 ; Point FSR at AD9850 movwf FSR ; next_byte movf INDF,w ; movwf byte2send ; movlw 0x08 ; Set counter to 8 movwf bit_count ; next_bit rrf byte2send,f ; Test if next bit is 1 or 0 btfss STATUS,B_C ; Was it zero? goto send0 ; Yes, send zero bsf PortB,7 ; No, send one bsf PortB,5 ; Toggle write clock bcf PortB,5 ; goto break ; send0 bcf PortB,7 ; Send zero bsf PortB,5 ; Toggle write clock bcf PortB,5 ; break decfsz bit_count,f ; Has the whole byte been sent? goto next_bit ; No, keep going. incf FSR,f ; Start the next byte unless finished movlw AD9850_4+1 ; Next byte (past the end) subwf FSR,w ; btfss STATUS,B_C ; goto next_byte ; bsf PortB,0 ; Send load signal to the AD9850 bcf PortB,0 ; return ; ; ; ***************************************************************************** ; * * ; * Purpose: This subroutine converts a 32 bit binary number to a 10 digit * ; * BCD number. The input value taken from freq(0 to 3) is * ; * preserved. The output is in BCD(0 to 4), each byte holds => * ; * (hi_digit,lo_digit), most significant digits are in BCD_4. * ; * This routine is a modified version of one described in * ; * MicroChip application note AN526. * ; * * ; * Input: The value in freq_0 ... freq_3 * ; * * ; * Output: The BCD number in BCD_0 ... BCD_4 * ; * * ; ***************************************************************************** ; bin2BCD movlw 0x20 ; Set loop counter movwf BCD_count ; to 32 clrf BCD_0 ; Clear output clrf BCD_1 ; " " clrf BCD_2 ; " " clrf BCD_3 ; " " clrf BCD_4 ; " " bin_loop bcf STATUS,B_C ; Clear carry bit in STATUS ; ; Rotate bits in freq bytes. Move from LS byte (freq_0) to next byte (freq_1). ; Likewise, move from freq_1 to freq_2 and from freq_2 to freq_3. ; rlf freq_0,f ; Rotate left, 0 -> LS bit, MS bit -> Carry rlf freq_1,f ; Rotate left, Carry->LS bit, MS bit->Carry rlf freq_2,f ; Rotate left, Carry->LS bit, MS bit->Carry rlf freq_3,f ; Rotate left, Carry->LS bit, MS bit->Carry btfsc STATUS,B_C ; Is Carry clear? If so, skip next instruction bsf freq_0,0 ; Carry is set so wrap and set bit 0 in freq_0 ; ; Build BCD bytes. Move into LS bit of BCD bytes (LS of BCD_0) from MS bit of ; freq_3 via the Carry bit. ; rlf BCD_0,f ; Rotate left, Carry->LS bit, MS bit->Carry rlf BCD_1,f ; Rotate left, Carry->LS bit, MS bit->Carry rlf BCD_2,f ; Rotate left, Carry->LS bit, MS bit->Carry rlf BCD_3,f ; Rotate left, Carry->LS bit, MS bit->Carry rlf BCD_4,f ; Rotate left, Carry->LS bit, MS bit->Carry decf BCD_count,f ; Decrement loop count btfss STATUS,B_Z ; Is loop count now zero? goto adjust ; No, go to adjust return ; Yes, EXIT ; ============================================================================ adjust ; Internal subroutine, called by bin2BCD main loop only ; ; As BCD bytes are being built, make sure the nibbles do not grow larger than 9. ; If a nibble gets larger than 9, increment to next higher nibble. ; (If the LS nibble of a byte overflows, increment the MS nibble of that byte.) ; (If the MS nibble of a byte overflows, increment the LS nibble of next byte.) ; movlw BCD_0 ; Get pointer to BCD_0 movwf FSR ; Put pointer in FSR for indirect addressing call adj_BCD ; incf FSR,f ; Move indirect addressing pointer to BCD_1 call adj_BCD ; incf FSR,f ; Move indirect addressing pointer to BCD_2 call adj_BCD ; incf FSR,f ; Move indirect addressing pointer to BCD_3 call adj_BCD ; incf FSR,f ; Move indirect addressing pointer to BCD_4 call adj_BCD ; goto bin_loop ; Back to main loop of bin2BCD ; ============================================================================ adj_BCD ; Internal subroutine, called by adjust only movlw 3 ; Add 3 addwf INDF,w ; to LS digit movwf BCD_temp ; Save in temp btfsc BCD_temp,3 ; Is LS digit + 3 > 7 (Bit 3 set) movwf INDF ; Yes, save incremented value as LS digit movlw 0x30 ; Add 3 addwf INDF,w ; to MS digit movwf BCD_temp ; Save as temp btfsc BCD_temp,7 ; Is MS digit + 3 > 7 (Bit 7 set) movwf INDF ; Yes, save incremented value as MS digit return ; Return to adjust subroutine ; ; ***************************************************************************** ; * * ; * Purpose: Display the frequency setting on the LCD. * ; * * ; * Input: The values in BCD_4 ... BCD_0 * ; * * ; * Output: The number displayed on the LCD * ; * * ; ***************************************************************************** ; show_freq movlw 0x81 ; Point the LCD to first LCD digit location call cmnd2LCD ; Send starting digit location to LCD ; ; Running 4-bit mode, so need to send Most Significant Nibble first. ; ; Extract and send "XXXX" from byte containing "XXXXYYYY" ; - Swap halves to get YYYYXXXX ; - Mask with 0x0F to get 0000XXXX ; - Add ASCII bias (0030XXXX) ; swapf BCD_3,w ; Swap 10MHz BCD digit into lower nibble of W andlw 0x0F ; Mask for lower nibble only (0000XXXX) addlw 0x30 ; Add offset for ASCII char set (0030XXXX) call data2LCD ; Send byte in W to LCD ; ; Extract and send "YYYY" from byte containing "XXXXYYYY" ; - Mask with 0x0F to get 0000YYYY ; - Add offset for ASCII character set in LCD (0030YYYY) ; movf BCD_3,w ; Put 1MHz BCD digit into lower nibble of W andlw 0x0F ; Mask for lower nibble only (0000YYYY) addlw 0x30 ; Add offset for ASCII char set (0030YYYY) call data2LCD ; Send byte in W to LCD ; movlw ',' ; Get a comma call data2LCD ; Send byte in W to LCD ; swapf BCD_2,w ; Swap 100KHz BCD digit into lower nibble of W andlw 0x0F ; Mask for lower nibble only (0000XXXX) addlw 0x30 ; Add offset for ASCII char set (0030XXXX) call data2LCD ; Send byte in W to LCD ; movf BCD_2,w ; Put 10KHz BCD digit into lower nibble of W andlw 0x0F ; Mask for lower nibble only (0000YYYY) addlw 0x30 ; Add offset for ASCII char set (0030YYYY) call data2LCD ; Send byte in W to LCD ; swapf BCD_1,w ; Swap 1KHz BCD digit into lower nibble of W andlw 0x0F ; Mask for lower nibble only (0000XXXX) addlw 0x30 ; Add offset for ASCII char set (0030XXXX) call data2LCD ; Send byte in W to LCD ; movlw '.' ; Set up W with ASCII Period call data2LCD ; Send data byte in W to LCD ; movlw 0xC0 ; Point to LCD digit number nine call cmnd2LCD ; Send command byte in W to LCD ; movf BCD_1,w ; Put 100 Hz BCD digit into lower nibble of W andlw 0x0F ; Mask for lower nibble only (0000YYYY) addlw 0x30 ; Add offset for ASCII char set (0030YYYY) call data2LCD ; Send data byte in W to LCD ; swapf BCD_0,w ; Swap 10 Hz BCD digit into lower nibble of W andlw 0x0F ; Mask for lower nibble only (0000XXXX) addlw 0x30 ; Add offset for ASCII char set (0030XXXX) call data2LCD ; Send data byte in W to LCD ; movf BCD_0,w ; Put 1 Hz BCD digit into lower nibble of W andlw 0x0F ; Mask for lower nibble only (0000YYYY) addlw 0x30 ; Add offset for ASCII char set (0030YYYY) call data2LCD ; Send byte in W to LCD ; movlw ' ' ; Send a space call data2LCD ; to LCD ; movlw 'k' ; Send a 'k' call data2LCD ; to LCD ; movlw 'H' ; Send an "H" call data2LCD ; to LCD ; movlw 'z' ; Send a 'z' call data2LCD ; to LCD ; return ; ; ; ***************************************************************************** ; * * ; * Purpose: Check if LCD is done with the last operation. * ; * This subroutine polls the LCD busy flag to determine if * ; * previous operations are completed. * ; * * ; * Input: None * ; * * ; * Output: PortB set as: RB7..RB4 inputs * ; * RB3..RB0 outputs * ; ***************************************************************************** ; busy_check clrf PortB ; Clear all outputs on PortB bsf STATUS,B_RP0 ; Switch to bank 1 for Tristate operation movlw b'11110000' ; Set RB7..RB4 as inputs, RB3..RB0 outputs movwf TRISB ; via Tristate bcf STATUS,B_RP0 ; Switch back to bank 0 bcf PortB,LCD_rs ; Set up LCD for Read Busy Flag (RS = 0) bsf PortB,LCD_rw ; Set up LCD for Read (RW = 1) movlw 0xFF ; Set up constant 255 movwf timer1 ; for timer loop counter LCD_is_busy bsf PortB,LCD_e ; Set E high movf PortB,w ; Read PortB into W movwf LCD_read ; Save W for later testing bcf PortB,LCD_e ; Drop E again nop ; Wait a nop ; while bsf PortB,LCD_e ; Pulse E high (dummy read of lower nibble), nop ; wait, bcf PortB,LCD_e ; and drop E again decf timer1,f ; Decrement loop counter btfsc STATUS,B_Z ; Is loop counter down to zero? goto not_busy ; If yes, return regardless btfsc LCD_read,7 ; Is Busy Flag (RB7) in save byte clear? goto LCD_is_busy ; If not, it is busy so jump back not_busy return ; ; ; ***************************************************************************** ; * Purpose: Send Command or Data byte to the LCD * ; * Entry point cmnd2LCD: Send a Command to the LCD * ; * Entry Point data2LCD: Send a Data byte to the LCD * ; * * ; * Input: W has the command or data byte to be sent to the LCD. * ; * * ; * Output: None * ; ***************************************************************************** ; cmnd2LCD ; ****** Entry point ****** movwf LCD_char ; Save byte to write to LCD clrf rs_value ; Remember to clear RS (clear rs_value) bcf PortB,LCD_rs ; Set RS for Command to LCD goto write2LCD ; Go to common code data2LCD ; ****** Entry point ******** movwf LCD_char ; Save byte to write to LCD bsf rs_value,0 ; Remember to set RS (set bit 0 of rs_value) bsf PortB,LCD_rs ; Set RS for Data to LCD write2LCD call busy_check ; Check to see if LCD is ready for new data clrf PortB ; Clear all of Port B (inputs and outputs) bsf STATUS,B_RP0 ; Switch to bank 1 for Tristate operation movlw 0x00 ; Set up to enable PortB data pins movwf TRISB ; All pins (RB7..RB0) are back to outputs bcf STATUS,B_RP0 ; Switch to bank 0 bcf PortB,LCD_rw ; Set LCD back to Write mode (RW = 0) bcf PortB,LCD_rs ; Guess RS should be clear btfsc rs_value,0 ; Should RS be clear? (is bit 0 == 0?) bsf PortB,LCD_rs ; No, set RS ; ; Transfer Most Significant nibble (XXXX portion of XXXXYYYY) ; movlw 0x0F ; Set up mask andwf PortB,f ; Clear old RB7..RB4 movf LCD_char,w ; Put byte of data into W andlw 0xF0 ; Mask to give XXXX0000 in W iorwf PortB,f ; Send to RB7..RB4 without changing RB3..RB0 bsf PortB,LCD_e ; Pulse the E line high, nop ; wait, bcf PortB,LCD_e ; and drop it again ; ; Transfer Least Significant nibble (YYYY portion of XXXXYYYY) ; movlw 0x0F ; Set up mask andwf PortB,f ; Clear old RB7..RB4 swapf LCD_char,w ; Move LS nibble of data to MS position in W andlw 0xF0 ; Mask to give YYYY0000 in W iorwf PortB ; Send to RB7..RB4 without changing RB3..RB0 bsf PortB,LCD_e ; Pulse the E line high, nop ; wait, bcf PortB,LCD_e ; and drop it again return ; ; ***************************************************************************** ; * * ; * Purpose: Write the byte of data at EEdata to the EEPROM at address * ; * EEadr. * ; * * ; * Input: The values at EEdata and EEadr. * ; * * ; * Output: The EEPROM value is updated. * ; * * ; ***************************************************************************** ; write_EEPROM bsf STATUS,B_RP0 ; Switch to bank 1 bsf EEdata,WREN ; Set the EEPROM write enable bit movlw 0x55 ; Write 0x55 and 0xAA to EEPROM movwf EEadr ; control register, as required movlw 0xAA ; for the write movwf EEadr ; bsf EEdata,WR ; Set WR to initiate write bit_check btfsc EEdata,WR ; Has the write completed? goto bit_check ; No, keep checking bcf EEdata,WREN ; Clear the EEPROM write enable bit bcf STATUS,B_RP0 ; Switch to bank 0 incf EEadr,f ; Increment the EE write address return ; Return to the caller ; ; ***************************************************************************** ; * * ; * Purpose: Read a byte of EEPROM data at address EEadr into EEdata. * ; * * ; * Input: The address EEadr. * ; * * ; * Output: The value in EEdata. * ; * * ; ***************************************************************************** ; read_EEPROM bsf STATUS,B_RP0 ; Switch to bank 1 bsf EEdata,RD ; Request the read bcf STATUS,B_RP0 ; Switch to bank 0 incf EEadr,f ; Increment the read address return ; Return to the caller ; ; ***************************************************************************** ; * * ; * Purpose: Wait for a specified number of milliseconds. * ; * * ; * Entry point wait_128ms: Wait for 128 msec * ; * Entry point wait_64ms : Wait for 64 msec * ; * Entry point wait_32ms : Wait for 32 msec * ; * Entry point wait_16ms : Wait for 16 msec * ; * Entry point wait_8ms : Wait for 8 msec * ; * * ; * Input: None * ; * * ; * Output: None * ; * * ; ***************************************************************************** ; wait_128ms ; ****** Entry point ****** movlw 0xFF ; Set up outer loop movwf timer1 ; counter to 255 goto outer_loop ; Go to wait loops wait_64ms ; ****** Entry point ****** movlw 0x80 ; Set up outer loop movwf timer1 ; counter to 128 goto outer_loop ; Go to wait loops wait_32ms ; ****** Entry point ****** movlw 0x40 ; Set up outer loop movwf timer1 ; counter to 64 goto outer_loop ; Go to wait loops wait_16ms ; ****** Entry point ****** movlw 0x20 ; Set up outer loop movwf timer1 ; counter to 32 goto outer_loop ; Go to wait loops wait_8ms ; ****** Entry point ****** movlw 0x10 ; Set up outer loop movwf timer1 ; counter to 16 ; Fall through into wait loops ; ; Wait loops used by other wait routines ; - 1 microsecond per instruction (with a 4 MHz microprocessor crystal) ; - 510 instructions per inner loop ; - (Timer1 * 514) instructions (.514 msec) per outer loop ; - Round off to .5 ms per outer loop ; outer_loop movlw 0xFF ; Set up inner loop counter movwf timer2 ; to 255 inner_loop decfsz timer2,f ; Decrement inner loop counter goto inner_loop ; If inner loop counter not down to zero, ; then go back to inner loop again decfsz timer1,f ; Yes, Decrement outer loop counter goto outer_loop ; If outer loop counter not down to zero, ; then go back to outer loop again return ; Yes, return to caller ; ; ***************************************************************************** ; END