; **************************************************************************** ; * Signal Generator (VFO) with Direct Digital Synthesis * ; * Version 7b7 * ; * Jan 11, 2001 * ; * Dual PIC version, LCD Manager RT * * ; **************************************************************************** ; 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. This program ; is adapted from software written by NJ-QRP Club members. See author ; details below. ; ; The DDS and display processes are split across two PICs to increase speed ; of operation. This program drives the LCD and has been modified for an ; 8MHz clock. Note that a 2 line display is assumed. It is intended that the second ; line will be used as a Bar Graph S-Meter in a future development of this ; software. RT ; ;Version 7 Tidy up and insertion of timing measurement probes ; ;****************************************************************************** ; 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 and ; Craig Johnson, AA0ZZ ;***************************************************************************** ; ; ; Target Controller - PIC16F84 ; __________ ; PIC BUSY OUT --RA2 |1 18| RA1---------PIC 1 LCD DATA IN ; -------RA3 |2 17| RA0---------PIC 1 LCD CLOCK IN ; --------RA4 |3 16| OSC1--------XTAL ; Ground--------!MCLR |4 15| OSC2--------XTAL ; Ground----------Vss |5 14| VDD---------+5 V ; --------RB0 |6 13| RB7---------LCD 14 ; LCD_rs----------RB1 |7 12| RB6---------LCD 13 ; LCD_rw----------RB2 |8 11| RB5---------LCD 12 ; LCD_e-----------RB3 |9 10| RB4---------LCD 11 ; ---------- ;Note. Port B inputs require 10K pull up resistors to +5v. ; ; **************************************************************************** ; * 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. * ; **************************************************************************** ; ; ; ; **************************************************************************** ; * 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 ; **************************************************************************** ; * 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 BCD_count ; Used in bin2BCD routine BCD_temp ; " bit_count ; Used in serial inteface LCD_char ; Character being sent to the LCD LCD_read ; Character read from the LCD timer1 ; Used in delay routines timer2 ; " count ; loop counter (gets reused) rs_value ; The LCD rs line flag value rxbyte ; Used to receive data from PIC1 ; 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 ; ; ; ***************************************************************************** ; * * ; * Purpose: This is the start of the program. Ports and LCD are initialised * ; * before entering the main loop. * ; * * ; * * ; ***************************************************************************** ; start clrf rxbyte clrf INTCON ; No interrupts for now bsf STATUS,B_RP0 ; Switch to bank 1 bsf 0x01,7 ; Disable weak pullups movlw 0xF3 ; Set port A 0..1 inputs, 2..4 outputs 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 ; Fall into the Main Program Loop ; ; ***************************************************************************** ; * * ; * This routine receives the 4 bytes of freq data from PIC 1 and saves * ; * them in freq_0 to freq_3. Handshaking of the Busy line confirms * ; * the receipt of each bit. * ; * * ; * Input: Freq on Port B1 as serial data from PIC 1. * ; * * ; * Output: Freq data to the LCD routines for display * ; * * ; ***************************************************************************** ; ; Start of main loop ; Set up counters to clock data into freq main movlw 0x08 ; Set bit counter to 8 movwf bit_count movlw freq_0 ; Point FSR at freq movwf FSR ; bcf PortA,2 ; Drop Busy Line to signal to PIC 1 to send data ; Wait for PIC 1 LCD Data Clock line to go high next_bit btfss PortA,0 ; Check state of PIC 1 LCD Clock Line, is it high? goto next_bit ; No, re-test btfss PortA,1 ; Get state of PIC 1 LCD Data Line goto write_0 ; 0 goto write_1 ; 1 write_0 bcf rxbyte,7 ; goto break write_1 bsf rxbyte,7 ; break bsf PortA,2 ; Set Busy Line high to acknowledge receipt of bit ; Wait for PIC 1 LCD Data Clock line to go low Clock_lo_test btfsc PortA,0 ; Check state of PIC 1 LCD Clock Line, has it gone low? goto Clock_lo_test ; No, re-test bcf PortA,2 ; Set Busy Line low, ready to receive decfsz bit_count ; All 8 bits received? goto continue ; No keep going goto next_byte continue rrf rxbyte ; Shift byte right to position bits goto next_bit ; next_byte movf rxbyte,w ; Save byte in freq movwf INDF incf FSR,f ; Start the next byte unless finished movlw freq_3+1 ; Next byte (past the end) subwf FSR,w ; movlw 0x08 ; Set bit counter to 8 movwf bit_count btfss STATUS,B_C ; Have all four data bytes been received? goto next_bit ; No start next byte bsf PortA,2 ; Set Busy Line high (busy) to release PIC 1 bsf PortA,3 ; *** Test Probe for process timing measure bsf PortB,0 ; *** Test Probe for bin2BCD timing measure call bin2BCD ; Binary to BCD Conversion bcf PortB,0 ; *** Test Probe for bin2BCD timing measure call show_freq ; Send to LCD bcf PortA,3 ; *** Test Probe for process timing measure call wait_64ms ; Delay to reduce call rate on PIC 1 goto main ; Request new update, endless Loop ; ***************************************************************************** ; * * ; * 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 2 display. * ; * * ; * Input: None * ; * * ; * Output: None * ; * * ; ***************************************************************************** ; init_LCD call wait_64ms ; Wait for LCD to power up movlw 0x30 ; LCD init instruction (First) bsf PortB,LCD_e ; Set the LCD E line high, movwf PortB ; Send to LCD via RB7..RB0 call wait_64ms ; wait a "long" time, bcf PortB,LCD_e ; and then Clear E movlw 0x30 ; LCD init instruction (Second) bsf PortB,LCD_e ; Set E high, movwf PortB ; Send to LCD via RB7..RB0 call wait_32ms ; wait a while, bcf PortB,LCD_e ; and then Clear E movlw 0x30 ; LCD init instruction (Third) bsf PortB,LCD_e ; Set E high, movwf PortB ; Send to LCD via RB7..RB0 call wait_32ms ; wait a while, bcf PortB,LCD_e ; and then Clear E movlw 0x20 ; 4-bit mode instruction, bsf PortB,LCD_e ; Set E high, movwf PortB ; Send to LCD via RB7..RB0 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 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 0x82 ; 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 period 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 comma call data2LCD ; Send data byte in W to LCD ; ; movlw 0xC0 ; Point to LCD digit number nine (not needed 2 line display) ; 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 'M' ; Send a 'M' 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 nop movf PortB,w ; Read PortB into W movwf LCD_read ; Save W for later testing nop 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, nop ; 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 bsf PortB,LCD_e ; Pulse the E line high, nop iorwf PortB,f ; Send to RB7..RB4 without changing RB3..RB0 nop ; wait, nop ; 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 bsf PortB,LCD_e ; Pulse the E line high, nop iorwf PortB ; Send to RB7..RB4 without changing RB3..RB0 nop ; wait, nop ; bcf PortB,LCD_e ; and drop it again return ; ; ; ***************************************************************************** ; * * ; * 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 - modified for an 8MHz clock ; - 0.5 microsecond per instruction (with a 8 MHz microprocessor crystal) ; - 1020 instructions per inner loop ; - (Timer1 * 1020) 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 nop ; Null to pad out to 1 microsecond nop ; Null to pad out to 1 microsecond 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