;******************************************************************************* ; ; Project: ; PIC16C84 controller for AD9832 0-6 MHz DDS VFO ; ; File: ; ad9832_3.asm ; ; Description: ; The controller uses a rotary encoder, a dial lock switch, ; a step size switch and a HD44780 based 1x16 LCD. Serial ; data is sent via three lines to an AD9832 DDS chip. ; The LCD shows the frequency in Hz (for example 1.234.567) ; and the step size (S=1, S=10, S=100, S=1k or S=10k). ; Depending on the level on the RA4 pin, the LCD can show ; either the true output frequency or the output frequency ; divided by four. This only affects the display, actual ; output frequency doesn't change. The f/4 feature is ; intended for direct conversion receivers where the LO is ; divided by four in the front end (Dan Tayloe detector etc.). ; Fmax = 1.5 MHz may seem a little bit low but my primary area ; of interest is longwave ;-) ; ; This version of the program has no other bells and whistles ; such as IF offset band switching etc. ; ; The encoder is sampled every 256 us by a real time tick ISR. ; The ISR increments/decrements the 24-bit variable "freq" ; depending on encoder direction. The frequency step is held ; in the 16-bit variable "step". Pushing the step switch cycles ; through the avaliable step sizes 1, 10, 100, 1 k and 10 kHz. ; The WB2V version with variable tuning rate is more elegant ; but also more suited for encoders with lower resolution ; such as 32 PPR. I decided to use fixed tuning rate when I ; found two Bourns ball bearing (!) encoders in my junk box, ; 100 and 256 PPR respectively. ; ; Whenever the tick ISR changes the value of "freq", it will ; set the flag "f_chang". This flag will make the main loop ; update the LCD and the DDS chip once. In other words, the ; LCD and the DDS chip will only be updated when the frequency ; has changed. This is done to keep spurious radiation to a ; minimum. Updating LCD and DDS takes quite a lot of time but ; that is no problem since the tick ISR ensures that the "freq" ; variable will track the encoder all the time. ; ; My dial lock switch is a small three position SPDT lever ; switch, ON-OFF-(ON). The dial is locked when the switch is ; in mid-position. The dial is permanently unlocked in the ON ; position (up) and momentary unlocked in the (ON) position ; (down). ; ; Dependencies: ; The file p16c84.inc Rev.C JBE ; Development assembler: Microchip/Bytecraft MPASM 1.50 Released ; ; Notes: ; Written by Johan Bodin, SM6LKM (sm6lkm.jbeab@swipnet.se) ; Some of the code is stolen from Curtis W. Preuss, WB2V, ; (with permission!) and modified. ; ; The program will probably run a PIC16F84 as well if you ; make the necessary changes to the config fuse bits (See ; "Assembler directives", below). ; ; All timing is based on the use of a 4.000 MHz crystal. ; If you are a purist, you may want to run the PIC on a ; 3.125 MHz clock derived from the 25 MHz DDS clock (f/8 ; counter). This may help reduce spurious radiation. ; ; The LCD initialization code is written for a 1x16 LCD ; with 5x10 font (reason = found in junk box). ; ; See "I/O pin mapping" section below for PIC pin connections. ; ; The RP0 bit in the status register is 0 as default (RAM page 0). ; Code that sets the RP0 bit MUST clear it when finished accessing ; RAM page 1. ; ; Known bugs: ; None. Yet... ; ; - Revision history - ; ============================================================== ; Version Date Comment / Programmer ; -------------------------------------------------------------- ; 0.01 981214 Created / Johan Bodin, SM6LKM, WDT is off ; 0.02 981214 Bug in LCD busywait corrected. The second E ; pulse (for lower nibble) was never sent to ; LCD after detecting LCD ready. The LCD worked ; well despite this bug!? Can anyone explain ; *why* it worked? Why didn't the LCD get the ; nibbles in wrong order?... Is the hi/lo ; nibble mux reset whenever RS changes state? ; 0.03 981223 Fixed bug related to high encoder speed. ; calc_dds did not make atomic read of freq. ; Wasted three more RAM bytes for the freq_copy ; variable which is copied and used by calc_dds ; and then again used as input for bin2bcd. ; ============================================================== ; ;******************************************************************************* ;------------------------------------------------------------------------------- ; Assembler directives ;------------------------------------------------------------------------------- title "DDS VFO Controller" subtitle "AD9832" processor 16C84 include "p16c84.inc" ; EQUs for processor registers etc. radix dec ; decimal radix as default errorlevel 0 ; show all warnings and errors __idlocs 0xCAFE ; why not, it's better than 0x2BAD... __config _CP_OFF & _PWRTE_OFF & _WDT_OFF & _XT_OSC ;------------------------------------------------------------------------------- ; Naming of RAM variables ;------------------------------------------------------------------------------- CBLOCK 0x0C ; we have 36 bytes of RAM in the area 0x0C - 0x2F w_save ; W is saved here during tick ISR execution s_save ; STATUS is saved here during tick ISR execution enctmp ; temporary variable for use in timer tick ISR prevenc ; encoder state saved here between timer ticks temp0 ; temporary var's, used by many routines temp1 temp2 loopcnt ; loop counter, used by many routines freq0 ; 24-bit frequency (Hz), freq0 is LSbyte freq1 freq2 bcd0 ; 7-digit packed BCD, lower nibble of bcd0 is LSDigit bcd1 bcd2 bcd3 ; upper nibble of bcd3 is not used (8:th digit) dds0 ; 32-bit frequency control word for AD9832 DDS... dds1 ; ...dds0 is LSByte dds2 dds3 step0 ; 16-bit frequency step (Hz/encoder pulse)... step1 ; ...step0 is LSByte bcdtmp ; temporary variable used in binary-to-BCD conversion osc0 ; 32-bit calibration value = 2^56 / fclk (Hz)... osc1 ; ...osc0 is LSByte osc2 osc3 osc_copy0 ; osc is copied to osc_copy before multiplication... osc_copy1 ; ...beacuse osc_copy will be destroyed in the... osc_copy2 ; ...process osc_copy3 lcd_char ; used by LCD write routines flags ; various flag bits freq_copy0 ; calc_dds makes an atomic copy from freq to... freq_copy1 ; ...freq_copy which is later used (and destroyed)... freq_copy2 ; ...by bin2bcd ; We still have one byte of free RAM left! Wow! ENDC ; Naming of individual bits in the variable "flags" #define f_lstep flags,7 ; last level of step switch #define f_freg flags,6 ; used to alternate between AD9832 f. reg's #define f_chang flags,5 ; flags freq. change from ISR to main loop #define f_4 flags,4 #define f_3 flags,3 #define f_2 flags,2 #define f_1 flags,1 #define f_0 flags,0 ;------------------------------------------------------------------------------- ; I/O pin mapping and pin read/write macros ;------------------------------------------------------------------------------- ; PORTA pins ; RA4 0=display true frequency, 1=Display freq/4 (Tayloe etc.) ; RA3 LCD D7, AD9832 SDATA ; RA2 LCD D6, AD9832 SCLK ; RA1 LCD D5 ; RA0 LCD D4 ; PORTA pin read/write macros #define i_div4 PORTA,4 ; this pin (if used) needs a pull-up resistor #define o_sdata PORTA,3 #define o_sclk PORTA,2 #define o_ra1 PORTA,1 #define o_ra0 PORTA,0 ; PORTB pins (internal pullup is enabled for all PORTB inputs) ; RB7 AD9832 FSYNC ; RB6 LCD E ; RB5 LCD RW ; RB4 LCD RS ; RB3 Dial lock switch to ground, 1 = locked ; RB2 Step switch to ground ; RB1 Encoder phase I ; RB0 Encoder phase Q ; PORTB pin read/write macros #define o_fsync PORTB,7 #define o_e PORTB,6 #define o_rw PORTB,5 #define o_rs PORTB,4 #define i_lock PORTB,3 #define i_step PORTB,2 #define i_rb1 PORTB,1 #define i_rb0 PORTB,0 ; Pin directions after power-up, 0=output, 1=input PUTRISA equ B'00010000' ; all PORTA pins except RA4 are outputs PUTRISB equ B'00001111' ; upper nibble of PORTB are outputs ; Output pin levels after power-up PUPORTA equ B'00000000' PUPORTB equ B'10000000' ; AD9832 FSYNC pin idles high ;------------------------------------------------------------------------------- ; Reset entry point ;------------------------------------------------------------------------------- ORG 0x0000 goto init ;------------------------------------------------------------------------------- ; Realtime tick ISR (256 us = 3906.25 Hz) ; ; The ISR (interrupt service routine) runs a quadrature ; decoder which looks at the encoder input. ; ; The 24-bit value "freq" is incremented/decremented by ; the 16-bit value "step" depending on encoder direction. ; The result in "freq" is clipped at 0 and 6 MHz. ; ; Assuming 50% duty cycle and exactly 90 degrees phase ; offset between encoder channels, maximum knob speed ; is 1/(PPR*1024e-6) revolutions/s (PPR is number of ; encoder pulses per revolution). ; ; Example: A 100PPR encoder will work up to 9.77 revolutions ; per second (585 RPM) without missing a single step. As the ; quadrature decoder counts every edge, a 100 PPR encoder ; will give 400 frequency steps per revolution. ;------------------------------------------------------------------------------- ORG 4 movwf w_save ; save important registers swapf STATUS,w bcf _RP0 movwf s_save bcf _T0IF ; clear TMR0 interrupt flag ; Check encoder movf PORTB,w ; read current encoder state into... movwf enctmp ; ...enctmp (bits 0 and 1) bcf enctmp,2 ; copy previous encoder state into... btfsc prevenc,0 ; ...enctmp (bits 2 and 3) bsf enctmp,2 bcf enctmp,3 btfsc prevenc,1 bsf enctmp,3 movwf prevenc ; save current encoder state for next tick btfsc i_lock ; dial locked? goto enc_done ; yes, skip inc/dec stuff movf enctmp,w ; get combination of previous and new state andlw 0x0F ; remove garbage from upper nibble addwf PCL,f ; bounce in jump table below goto enc_done ; 00 -> 00 = no change goto enc_inc ; 00 -> 01 = increment goto enc_dec ; 00 -> 10 = decrement goto enc_done ; 00 -> 11 = illegal, ignore goto enc_dec ; 01 -> 00 = decrement goto enc_done ; 01 -> 01 = no change goto enc_done ; 01 -> 10 = illegal, ignore goto enc_inc ; 01 -> 11 = increment goto enc_inc ; 10 -> 00 = increment goto enc_done ; 10 -> 01 = illegal, ignore goto enc_done ; 10 -> 10 = no change goto enc_dec ; 10 -> 11 = decrement goto enc_done ; 11 -> 00 = illegal, ignore goto enc_dec ; 11 -> 01 = decrement goto enc_inc ; 11 -> 10 = increment goto enc_done ; 11 -> 11 = no change enc_dec: bsf f_chang ; tell main loop to update LCD & DDS movf step0,w ; freq -= step subwf freq0,f movf step1,w btfss _C incfsz step1,w subwf freq1,f btfss _C decf freq2,f btfss freq2,7 ; negative frequency? goto enc_done clrf freq0 ; yes, clip at 0 Hz clrf freq1 clrf freq2 goto enc_done enc_inc: bsf f_chang ; tell main loop to update LCD & DDS movf step0,w ; freq += step addwf freq0,f movf step1,w btfsc _C incfsz step1,w addwf freq1,f btfsc _C incf freq2,f ; If freq > 6 MHz then set freq = 6 MHz (0x5B8D80) movf freq2,w sublw 0x5B ; freq2 > 0x5B? btfss _C goto setmax ; yes, go clip the value btfss _Z ; freq2 == 0x5B? goto enc_done ; no, freq2 < 0x5B, it's okay movf freq1,w sublw 0x8D ; freq1 > 0x8D? btfss _C goto setmax ; yes, go clip the value btfss _Z ; freq1 == 0x8D? goto enc_done ; no, freq1 < 0x8D, it's okay movf freq0,w sublw 0x80 ; freq0 > 0x80? btfsc _C goto enc_done ; no, freq0 <= 0x80, it's okay setmax: movlw 0x5B ; set freq = 6 MHz movwf freq2 movlw 0x8D movwf freq1 movlw 0x80 movwf freq0 enc_done: swapf s_save,w ; restore important registers movwf STATUS swapf w_save,f swapf w_save,w retfie ;------------------------------------------------------------------------------- ; Initialization ;------------------------------------------------------------------------------- init: ; Set pin output latches movlw PUPORTA movwf PORTA movlw PUPORTB movwf PORTB ; Set pin directions and set up OPTION register bsf _RP0 ; select RAM page 1 movlw PUTRISA ; pin directions for PORTA movwf 0x7F & TRISA movlw PUTRISB ; pin directions for PORTB movwf 0x7F & TRISB movlw B'01011111' ; PORTB pullup, internal TMR0 clk, PS on WDT movwf 0x7F & OPTI bcf _RP0 ; RAM page 0 is default ; Initialize the LCD, HD44780 controller, 4-bit mode ; (E, R/W and RS are low when we get here) call delay100 ; allow 100 ms for LCD power-up movlw 0x30 ; function set, 8-bit interface movwf PORTA bsf o_e ; make E pulse bcf o_e call delay100 ; we can't check the busy bit yet movlw 0x30 ; again... movwf PORTA bsf o_e bcf o_e call delay100 movlw 0x30 ; ...and again movwf PORTA bsf o_e bcf o_e call delay100 movlw 0x20 ; now when we are sure that we... movwf PORTA ; ...have an 8-bit interface... bsf o_e ; ...we can proceed to select... bcf o_e ; ...4-bit interface instead (oumph) call delay100 movlw 0x24 ; 4-bit interface, 1 line, 5x10 font... call lcd_ctrl ; ...change to 0x20 for 5x7 font LCD movlw 0x01 ; clear display, home cursor call lcd_ctrl movlw 0x06 ; increment mode, no display shift call lcd_ctrl movlw 0x0C ; display on, no cursor, no blink call lcd_ctrl ; Set initial frequency movlw 0x02 ; 135.7 kHz movwf freq2 movlw 0x12 movwf freq1 movlw 0x14 movwf freq0 ; Set calibration factor movlw 0xAB ; set osc = 2^56 / fclk, 25 MHz => 0xABCC7712 movwf osc3 ; Note! The value will be stored in EEPROM... movlw 0xCC ; ...in the next version of the program movwf osc2 movlw 0x77 movwf osc1 movlw 0x12 movwf osc0 ; Initialize AD9832 chip movlw 0xF8 ; SLEEP = RESET = CLR = 1 movwf temp1 ; (LSByte sent from temp0 is unimportant) call send_dds movlw 0xB0 ; SYNC = SELSRC = 1 movwf temp1 ; (LSByte sent from temp0 is unimportant) call send_dds movlw 0xC0 ; SLEEP = RESET = 0 movwf temp1 ; (LSByte sent from temp0 is unimportant) call send_dds ; Initialize encoder stuff movf PORTB,w ; avoid spurious encoder step at power-up movwf prevenc bsf _GIE ; global interrupt enable (T0IE bit... ; ...will be set in the main loop) ; Here we go! bsf f_chang ; first LCD & DDS update without knob touch movlw 0x8B ; set (invisible) cursor at character 11 call lcd_ctrl goto set_step_1h ; set initial step size to 1 Hz... ;------------------------------------------------------------------------------- ; Main program loop ;------------------------------------------------------------------------------- mainlooptop: ; Check the step switch, main loop execution time ; is used for debouncing. Maybe too little if your ; pushbutton is "bouncy". btfss i_step ; step switch pressed? goto chkedge bsf f_lstep ; no, keep this flag updated for next check goto compute chkedge: btfss f_lstep ; yes, did press occur since last check? goto clr_lstep ; Cycle through available step rates ; 1 Hz - 10 Hz - 100 Hz - 1 kHz - 10 kHz - 1 Hz etc. change_step: movlw 0x8B ; yes, set cursor at character 11 call lcd_ctrl bcf _T0IE ; TMR0 interrupt off (atomic update of step) btfsc step0,0 ; is step = 1 Hz? (0x0001) goto set_step_10h btfsc step0,1 ; is step = 10 Hz? (0x000A) goto set_step_100h btfsc step0,2 ; is step = 100 Hz? (0x0064) goto set_step_1k btfsc step0,7 ; is step = 1 kHz? (0x03E8) goto set_step_10k ; Step seems to be 10 kHz, set step back to 1 Hz set_step_1h: clrf step1 ; step = 0x0001 = 1 movlw 0x01 movwf step0 bsf _T0IE ; unmask TMR0 interrupt movlw 32 ; write " S=1" call lcd_data movlw 32 call lcd_data movlw 'S' call lcd_data movlw '=' call lcd_data movlw '1' call lcd_data goto clr_lstep set_step_10h: clrf step1 ; step = 0x000A = 10 movlw 0x0A movwf step0 bsf _T0IE ; unmask TMR0 interrupt movlw 32 ; write " S=10" call lcd_data movlw 'S' call lcd_data movlw '=' call lcd_data movlw '1' call lcd_data movlw '0' call lcd_data goto clr_lstep set_step_100h: clrf step1 ; step = 0x0064 = 100 movlw 0x64 movwf step0 bsf _T0IE ; unmask TMR0 interrupt movlw 'S' ; write "S=100" call lcd_data movlw '=' call lcd_data movlw '1' call lcd_data movlw '0' call lcd_data movlw '0' call lcd_data goto clr_lstep set_step_1k: movlw 0x03 ; step = 0x03E8 = 1000 movwf step1 movlw 0xE8 movwf step0 bsf _T0IE ; unmask TMR0 interrupt movlw 32 ; write " S=1k" call lcd_data movlw 'S' call lcd_data movlw '=' call lcd_data movlw '1' call lcd_data movlw 'k' call lcd_data goto clr_lstep set_step_10k: movlw 0x27 ; step = 0x2710 = 10000 movwf step1 movlw 0x10 movwf step0 bsf _T0IE ; unmask TMR0 interrupt movlw 'S' ; write "S=10k" call lcd_data movlw '=' call lcd_data movlw '1' call lcd_data movlw '0' call lcd_data movlw 'k' call lcd_data clr_lstep: bcf f_lstep ; keep this flag updated for next check compute: btfss f_chang ; has frequency changed? goto dummydelay ; no, go eat some time bcf f_chang ; yes, clear change flag and go4it call calc_dds ; make freq_copy, compute DDS frequency word call load_dds ; send DDS frequency word to AD9832 call bin2bcd ; convert freq_copy to BCD call showfreq ; show BCD frequency on the LCD goto mainlooptop ; Wait about 10 ms to keep step switch debouncing ; working even when there is no new frequency to process. dummydelay: movlw 13 movwf temp0 dummydel1: movlw 255 movwf temp1 dummydel2: decfsz temp1,f goto dummydel2 decfsz temp0,f goto dummydel1 goto mainlooptop ;------------------------------------------------------------------------------- ; Convert frequency (Hz) to DDS word using calibration factor osc. ; This "QRP version" of WB2V's multiplication routine uses only ; 24 bits for frequency (Hz). The DDS word has been shrunk from ; 40 to 32 bits. However, the osc value is still a 32-bit number. ; ; This routine makes an atomic copy from freq to freq_copy. ; The copy is preserved during multiplication and is used ; later by the bin2bcd routine. ;------------------------------------------------------------------------------- calc_dds: bcf _T0IE ; this copying operation must be atomic in... movf freq0,w ; ...order to prevent the freq value from... movwf freq_copy0 ; ...beeing updated by ISR during... movf freq1,w ; ...multiplication movwf freq_copy1 movf freq2,w movwf freq_copy2 bsf _T0IE ; re-enable tick interrupt clrf dds0 ; clear multiplication accumulator clrf dds1 clrf dds2 clrf dds3 movlw 32 ; initialize multiplication loop counter movwf loopcnt movf osc0,w ; copy osc to osc_copy because the value... movwf osc_copy0 ; ...will be destroyed in the process... movf osc1,w movwf osc_copy1 movf osc2,w movwf osc_copy2 movf osc3,w movwf osc_copy3 multloop: bcf _C btfss osc_copy0,0 ; add the weight of this osc bit? goto shiftit movf freq_copy0,w ; dds += (freq_copy << 8) addwf dds1,f movf freq_copy1,w btfsc _C incfsz freq_copy1,w addwf dds2,f movf freq_copy2,w btfsc _C incfsz freq_copy2,w addwf dds3,f shiftit: rrf dds3,f ; align dds and osc_copy for... rrf dds2,f ; ...next binary weight rrf dds1,f rrf dds0,f rrf osc_copy3,f rrf osc_copy2,f rrf osc_copy1,f rrf osc_copy0,f decfsz loopcnt,f ; done? goto multloop return ;------------------------------------------------------------------------------- ; Update DDS chip with a new frequency word ; ; Note! Since there is no way to update all 32 bits simultaneously ; in the AD9832, we have to toggle between the two frequency ; registers. Example: Load reg0, switch to reg0, load reg1, switch ; to reg1 etc. etc. This is done to prevent glitches occurring when ; two or more bytes change value at the same time. ; (See AD9832 data sheet...) ;------------------------------------------------------------------------------- load_dds: btfss f_freg ; frequency register 0 updated last time? goto load_reg1 ; yes, go update register 1 this time bcf f_freg ; update register 0 this time, 1 next time movlw 0x30 ; write to defer register, destination = 0 movwf temp1 movf dds0,w ; low byte movwf temp0 call send_dds ; talk to the hardware movlw 0x21 ; write 16 freq. bits, destination = 1 movwf temp1 movf dds1,w ; low mid byte movwf temp0 call send_dds ; talk to the hardware movlw 0x32 ; write to defer register, destination = 2 movwf temp1 movf dds2,w ; high mid byte movwf temp0 call send_dds ; talk to the hardware movlw 0x23 ; write 16 freq. bits, destination = 3 movwf temp1 movf dds3,w ; high byte movwf temp0 call send_dds ; talk to the hardware movlw 0x50 ; switch to frequency register 0 movwf temp1 ; (data in temp0 is unimportant) call send_dds ; talk to the hardware return load_reg1: bsf f_freg ; update register 1 this time, 0 next time movlw 0x34 ; write to defer register, destination = 4 movwf temp1 movf dds0,w ; low byte movwf temp0 call send_dds ; talk to the hardware movlw 0x25 ; write 16 freq. bits, destination = 5 movwf temp1 movf dds1,w ; low mid byte movwf temp0 call send_dds ; talk to the hardware movlw 0x36 ; write to defer register, destination = 6 movwf temp1 movf dds2,w ; high mid byte movwf temp0 call send_dds ; talk to the hardware movlw 0x27 ; write 16 freq. bits, destination = 7 movwf temp1 movf dds3,w ; high byte movwf temp0 call send_dds ; talk to the hardware movlw 0x58 ; switch to frequency register 1 movwf temp1 ; (data in temp0 is unimportant) call send_dds ; talk to the hardware return ;------------------------------------------------------------------------------- ; Send a 16-bit word (temp1:temp0) to the DDS chip ;------------------------------------------------------------------------------- send_dds: movlw 16 movwf loopcnt bcf o_fsync ; pull AD9832 FSYNC pin low send_loop: bcf o_sdata ; assume zero bit, AD9832 SDATA pin = 0 btfsc temp1,7 ; check MSBit of 16-bit word bsf o_sdata ; oops, it was a 1, AD9832 SDATA pin = 1 bsf o_sclk ; make a pulse on AD9832 SCLK pin bcf o_sclk rlf temp0,f ; rotate all 16 bits left (through carry) rlf temp1,f decfsz loopcnt,f goto send_loop bsf o_fsync ; pull AD9832 FSYNC pin high return ;------------------------------------------------------------------------------- ; Show frequency on LCD (seven rightmost digits in the bcd buffer) ;------------------------------------------------------------------------------- showfreq: movlw 0x80 ; set cursor at leftmost character position call lcd_ctrl movf bcd3,w ; read 1M BCD digit into lower nibble of W andlw 0x0F ; mask for lower nibble addlw 0x30 ; add ASCII offset call lcd_data ; send to LCD movlw '.' call lcd_data ; dot between 1M and 100k digits swapf bcd2,w ; read 100k BCD digit into lower nibble of W andlw 0x0F ; mask for lower nibble addlw 0x30 ; add ASCII offset call lcd_data ; send to LCD movf bcd2,w ; read 10k BCD digit into lower nibble of W andlw 0x0F ; mask for lower nibble addlw 0x30 ; add ASCII offset call lcd_data ; send to LCD swapf bcd1,w ; read 1k BCD digit into lower nibble of W andlw 0x0F ; mask for lower nibble addlw 0x30 ; add ASCII offset call lcd_data ; send to LCD movlw '.' call lcd_data ; dot between 1k and 100H digits movf bcd1,w ; read 100H BCD digit into lower nibble of W andlw 0x0F ; mask for lower nibble addlw 0x30 ; add ASCII offset call lcd_data ; send to LCD swapf bcd0,w ; read 10H BCD digit into lower nibble of W andlw 0x0F ; mask for lower nibble addlw 0x30 ; add ASCII offset call lcd_data ; send to LCD movf bcd0,w ; read 1H BCD digit into lower nibble of W andlw 0x0F ; mask for lower nibble addlw 0x30 ; add ASCII offset call lcd_data ; send to LCD return ;------------------------------------------------------------------------------- ; 24-bit binary to BCD conversion. ; ; Binary input data is taken from freq_copy which is destroyed. ; The result is stored in bcd0..bcd3, bcd0 holds the two LSDigits. ; ; This is a modified 24-bit version of WB2V's 32-bit routine. ; The input value is NOT preserved by putting back the shifted-out ; bits into bit 0 (as was done in the original WB2V version). ;------------------------------------------------------------------------------- bin2bcd: btfss i_div4 ; divide reading by four? goto do_b2b ; no rrf freq_copy2,f ; yes, shift binary value two bits right rrf freq_copy1,f rrf freq_copy0,f rrf freq_copy2,f rrf freq_copy1,f rrf freq_copy0,f movlw 0x3F andwf freq_copy2,f ; clear garbage shifted in do_b2b: movlw 24 ; is clear carry really needed here?... movwf loopcnt ; ...it seems to work without it clrf bcd0 clrf bcd1 clrf bcd2 clrf bcd3 bcdloop: rlf freq_copy0,f ; rotate copy of freq through carry... rlf freq_copy1,f rlf freq_copy2,f rlf bcd0,f ; ...into bcd rlf bcd1,f rlf bcd2,f rlf bcd3,f decfsz loopcnt,f goto adjust return adjust: movlw bcd0 ; make magic adjustment to each BCD digit movwf FSR call adjbcd incf FSR,f call adjbcd incf FSR,f call adjbcd incf FSR,f call adjbcd goto bcdloop adjbcd: movlw 0x03 ; this "original" version of adjbcd (from... addwf INDF,w ; ...MicroChip's AN526 works fine here... movwf bcdtmp ; ...while WB2V's version does not!? Can... btfsc bcdtmp,3 ; ...anyone explain this? movwf INDF movlw 0x30 addwf INDF,w movwf bcdtmp btfsc bcdtmp,7 movwf INDF return ;------------------------------------------------------------------------------- ; Wait until LCD is ready for data ;------------------------------------------------------------------------------- busywait: bsf _RP0 movlw 0x1F ; tristate RA0..RA3 movwf 0x7F & TRISA bcf _RP0 bcf o_rs ; select LCD command register bsf o_rw ; setup LCD for read busyloop: bsf o_e ; E up movf PORTA,w ; read upper nibble, busy bit is in W bit 3 bcf o_e ; E down bsf o_e ; E up, dummy read of lower nibble bcf o_e ; E down andlw 0x08 ; W &= 0x08, mask for busy bit btfss _Z ; zero? goto busyloop ; no, keep on waiting bcf o_rw ; setup LCD for write bsf _RP0 movlw 0x10 ; enable RA0..RA3 again movwf 0x7F & TRISA bcf _RP0 return ;------------------------------------------------------------------------------- ; Send data/command byte in W to the LCD ;------------------------------------------------------------------------------- lcd_ctrl: movwf lcd_char ; save the byte to send call busywait bcf o_rs ; select LCD command register goto lcd_out ; use the code below lcd_data: movwf lcd_char ; save the byte to send call busywait bsf o_rs ; select LCD data register lcd_out: swapf lcd_char,w ; write MSnibble of data to RA0..RA3 movwf PORTA bsf o_e ; make E pulse bcf o_e movf lcd_char,w ; write LSnibble of data to RA0..RA3 movwf PORTA bsf o_e ; make E pulse bcf o_e return ;------------------------------------------------------------------------------- ; 100 ms delay subroutine (100 ms including call & return) ;------------------------------------------------------------------------------- delay100: movlw 198 ; 99996 cycle delay starts here movwf temp0 d100_1: movlw 167 movwf temp1 d100_2: decfsz temp1,F goto d100_2 decfsz temp0,F goto d100_1 goto $+1 goto $+1 nop return ;------------------------------------------------------------------------------- END