;****************************************************************************** ; Source code for Direct Digital Synthesis VFO controller ; ; **** NOW PORTED TO PIC16C84 **** (see revision note for 10/01/98) ; ; This code is available as is. ; ; Description: ; The program main loop, (Check_Encoder) polls a rotary shaft encoder. ; If the input from the shaft encoder has changed, then direction (up/down) ; is determined. The program then computes the corresponding ; frequency digits to be displayed on the Liquid Crystal Display. ; This data is transfered to the LCD using a 4 bit interface. ; A new DDS control word is then computed and transfered ; serially to the AD9850 DDS chip. ; The program then returns to the shaft encoder polling loop. ; ; Target Controller - PIC1654A (originally) ; Program Size - 474 words, 38 words free ; Assembler - Parallax SPASM v4.2 ; Author - Curtis W. Preuss - WB2V ; Initial creation 11/08/96 ; Modification History ; 11/24/96 Finished debugging ; 12/14/96 Edited comments ; 10/01/98 Ported source to PIC16C84 -- G. Heron, N2APB ; 1) Needed to shift the variables down by 5 bytes due to ; changes in memory architecture ; 2) Needed to modify some of the indirect addressing "reads" ; during compares to list ends (16C84 has more FSR bits) ; 3) Changed assembler directive device code ; 4) Added 2 more function sets during LCD init, per the data ; sheets for my wacko (bargain basement) Seiko M1641 displays ; 5) Changed delays during LCD init (added wait60ms rtn), ; (again, due to wacko Seiko M1641 LCDs?) ; 6) Removed assembler directive for reset vector and just ; added "jmp start" at loc 0 and a new "org 5" to bypass ; some of the interrupt vector locs at start of code space ; 7) Added explicit and separate "setb LCD_rw" in Busy_check ; routine to more reliably read Busy bit. (Again, perhaps ; only because of my wacko model Seiko LCD.) ; 8) Slightly modified source formatting to make it easier ; (for me) to read ; 9) Comments & questions welcome! Email to: n2apb@amsat.org ; ;****************************************************************************** maclib 'p84.inc' ;Device type, options and reset vector DEVICE PIC16C84,XT_OSC,PROTECT_OFF,WDT_OFF ;Assign names to IO pins AD9850_fqu equ PortB.0 ;Update pin on AD9850 LCD_rs equ PortB.1 ;0=instruction, 1=data LCD_rw equ PortB.2 ;0=write, 1=read LCD_e equ PortB.3 ;0=disable, 1=enable AD9850_wc equ PortB.5 ;AD9850 write clock AD9850_dat equ PortB.7 ;AD9850 serial data input LCD_VDD equ PortA.3 ;Power to LCD module ;Allocate variables in general purpose register space org 0ch LCD_Dat ds 7 ;current Display data AD9850 ds 5 ;control word for the DDS chip OP1 ds 4 ;OP1 and OP2 are work space OP2 ds 1 ;--for the multiply subroutine temp1 ds 1 temp2 ds 1 temp3 ds 1 old ds 1 ;old/new encoder input data new ds 1 count1 ds 1 ;used for timing the shaft encoder count2 ds 1 org 0 jmp start org 5 ;*************************************************************************** ;Busy_check Subroutine ; ;LCD read/write operations are slooooow, this subroutine ;polls the LCD busy flag to determine if previous operations are completed. ;Note side effect that the LCD_rw and LCD_rs are left in write data mode. Busy_check mov !PortB,#0F0h ;tristate the 4 data pins setb LCD_rw ;raise the r/w bit to read the LCD :busy setb LCD_e mov temp1,PortB ;save first nibble of input data clrb LCD_e nop setb LCD_e ;get next nibble to keep LCD happy clrb LCD_e snb temp1.7 ;check for busy flag jmp :busy clrb LCD_rw ;leave LCD in Write Data mode setb LCD_rs ret ;**************************************************************************** ;Send Character to LCD Subroutine ; ;The character to be sent must have been placed in "temp2" prior to calling ;the subroutine. LCD_rw and LCD_rs must be set up prior to entry. SendChar mov !PortB,#0 ;enable PortB mov temp1,temp2 ;copy temp2 and PortB,#0Fh ;clear data nibble and temp2,#0F0h or PortB,temp2 ;load first nibble setb LCD_e ;transfer first nibble clrb LCD_e swap temp1 and PortB,#0Fh ;clear data nibble and temp1,#0F0h or PortB,temp1 setb LCD_e ;transfer second nibble clrb LCD_e ret ;**************************************************************************** ;BCD increment subroutine ; ;This routine does a binary coded decimal increment to the values in LCD_Dat. ;On entry the FSR register must point to the LCD_Dat digit to be incremented. ;If the result is over 20MHz the value is reset back to 20MHz. BCD_INC mov temp2,indirect add temp2,#1 mov temp1,temp2 add temp1,#6 jb DC,:carry ;check Digit carry and temp2,#0Fh mov indirect,temp2 cjae LCD_Dat+0,#2,:setmax ;test for max frequency ret :carry clr temp2 mov indirect,temp2 dec FSR jmp BCD_INC :setmax mov FSR,#18 ;set the display to 20,000.00 mov indirect,#0 dec FSR cja FSR,#12,:setmax+2 mov indirect,#2 ret ;*************************************************************************** ;BCD decrement subroutine ; ;This routine does a binary coded decimal decrement to the values in LCD_Dat. ;On entry the FSR register must point to the LCD_Dat digit to be decremented, ;(one of 10's, 100's, 1K, 10K or 100K digit) ;If the result is under 100KHz the value is reset back to 100KHz. BCD_DEC mov temp2,indirect sub temp2,#1 jnb DC,:carry ;check Digit carry mov indirect,temp2 cja LCD_Dat+0,#0,:exit ;test for min frequency cja LCD_Dat+1,#0,:exit cja LCD_Dat+2,#0,:exit jmp :setmin :exit ret :carry mov temp2,#9 mov indirect,temp2 dec FSR jmp BCD_DEC :setmin mov FSR,#18 ;set the display to 00,100.00 (was #14) mov indirect,#0 dec FSR cja FSR,#14,:setmin+2 ; (was #0EAh) mov indirect,#1 dec FSR mov indirect,#0 dec FSR mov indirect,#0 ret ;**************************************************************************** ;Delay 24ms subroutine Wait mov temp1,#40 ;use #40 for 24ms wait if clock is 4MHz :outer mov temp2,#200 ;use #200 for 24ms wait :inner djnz temp2,:inner djnz temp1,:outer ret ;**************************************************************************** ;Delay 60ms subroutine Wait60ms mov temp1,#100 ;use #40 for 24ms wait if clock is 4MHz :outer mov temp2,#200 ;use #200 for 24ms wait :inner djnz temp2,:inner djnz temp1,:outer ret ;*************************************************************************** ;32 bit add ; ;The value in registers OP1 is added to the current 32 bit value in AD9850 Add32 add AD9850+0,OP1+0 jnc :add1 ijnz AD9850+1,:add1 ijnz AD9850+2,:add1 inc AD9850+3 :add1 add AD9850+1,OP1+1 jnc :add2 ijnz AD9850+2,:add2 inc AD9850+3 :add2 add AD9850+2,OP1+2 jnc :add3 inc AD9850+3 :add3 add AD9850+3,OP1+3 ret ;*************************************************************************** ;Write Data Subroutine ; ;This subroutine does two things: ; (1) it sends the contents LCD_dat to the LCD, ; (LCD_Dat must contain BCD digits) ; (2) the contents of AD9850 (40 bits) are serial shifted ; into the AD9850 module Write_Data mov temp2,#83h ;LCD position for first Digit call Busy_Check clrb LCD_rs ;Set LCD for instruction write call SendChar mov temp2,LCD_Dat+0 or temp2,#20h ;send a space if first digit is zero cje temp2,#20h,:wd2 ;otherwise or temp2,#10h ;convert BCD to ASCII :wd2 call Busy_check call SendChar ;Send 10MHz digit mov temp2,LCD_Dat+1 or temp2,#30h call Busy_check call SendChar ;Send 1MHz digit mov temp2,#',' call Busy_check call SendChar ;Send a comma mov temp2,LCD_Dat+2 or temp2,#30h call Busy_check call SendChar ;Send 100KHz digit mov temp2,LCD_Dat+3 or temp2,#30h call Busy_check call SendChar ;Send 10KHz digit mov temp2,#0C0h ;Point LCD at Digit #9, (there is a ;gap in the LCD address) call Busy_Check clrb LCD_rs ;Set LCD for instruction write call SendChar mov temp2,LCD_Dat+4 or temp2,#30h call Busy_check call SendChar ;Send 1KHz digit mov temp2,#'.' call Busy_check call SendChar ;Send a period mov temp2,LCD_Dat+5 or temp2,#30h call Busy_check call SendChar ;Send 100Hz digit mov temp2,LCD_Dat+6 or temp2,#30h call Busy_check call SendChar ;Send 10Hz digit :Write_AD9850 clr PortB mov temp3,#5 ;send 5 bytes to 9850 mov !PortB,#0 ;enable PortB mov FSR,#19 ;point at AD9850+0 :next_byte mov temp1,indirect mov temp2,#8 ;set bit counter to 8 :next_bit rr temp1 jnc :send0 setb AD9850_dat setb AD9850_wc ;toggle write clock clrb AD9850_wc ;(repeated 40 times) jmp :break :send0 clrb AD9850_dat setb AD9850_wc clrb AD9850_wc :break decsz temp2 jmp :next_bit inc FSR djnz temp3,:next_byte ;through w/all 5 bytes? setb AD9850_fqu ;send load signal to AD9850 ;after last data dbit is written clrb AD9850_fqu ret ;***************************************************************************** ;7 Digit BCD by 32 bit binary mulitply ; ;The 7 BCD digits stored in LCD_dat are each multiplied by a precalculated digit ;weight, (Digit weigths assume a 66.666MHz input clock). The sum of all 7 ;digits times their weigth is stored in the AD9850 registers. Mult clr temp1 clr AD9850+0 clr AD9850+1 clr AD9850+2 clr AD9850+3 clr AD9850+4 :_10 mov OP1+0,#84h mov OP1+1,#02h clr OP1+2 clr OP1+3 mov OP2,LCD_Dat+6 jmp :go :_100 mov OP1+0,#2Ah mov OP1+1,#19h clr OP1+2 clr OP1+3 mov OP2,LCD_Dat+5 jmp :go :_1K mov OP1+0,#0A9h mov OP1+1,#0FBh clr OP1+2 clr OP1+3 mov OP2,LCD_Dat+4 jmp :go :_10K mov OP1+0,#9Bh mov OP1+1,#0D4h mov OP1+2,#09h clr OP1+3 mov OP2,LCD_Dat+3 jmp :go :_100K mov OP1+0,#13h mov OP1+1,#4Eh mov OP1+2,#62h clr OP1+3 mov OP2,LCD_Dat+2 jmp :go :_1M mov OP1+0,#0C1h mov OP1+1,#0Ch mov OP1+2,#0D7h mov OP1+3,#03h mov OP2,LCD_Dat+1 jmp :go :_10M mov OP1+0,#90h mov OP1+1,#7Fh mov OP1+2,#66h mov OP1+3,#26h mov OP2,LCD_Dat+0 jmp :go :go mov temp2,#4 :digit_loop clc rr OP2 jnc :noCarry call Add32 :noCarry clc rl OP1+0 rl OP1+1 rl OP1+2 rl OP1+3 decsz temp2 jmp :digit_loop inc temp1 cje temp1,#1,:_100 cje temp1,#2,:_1K cje temp1,#3,:_10K cje temp1,#4,:_100K cje temp1,#5,:_1M cje temp1,#6,:_10M ret ;**************************************************************************** ;Initialize PIC controller ; ; Start here after reset or power on ; Set DDS initially to 10,000.00 Start clr PortA ;clear data port A clr PortB ;clear data port B mov !PortB,#0 ;enable port B drivers mov !PortA,#255 ;trisate port A drivers ;Initalize Variables mov LCD_Dat+0 ,#1 mov LCD_Dat+1 ,#0 mov LCD_Dat+2 ,#0 mov LCD_Dat+3 ,#0 mov LCD_Dat+4 ,#0 mov LCD_Dat+5 ,#0 mov LCD_Dat+6 ,#0 clr count1 clr count2 ;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. mov !PortA,#07h ;Enable LCD_VDD pin setb LCD_VDD ;power up LCD call Wait60ms ;Wait for LCD to power up setb LCD_e mov PortB,#38h ;func set + enable to LCD clrb LCD_e call Wait60ms setb LCD_e mov PortB,#38h ;func set + enable to LCD clrb LCD_e call Wait60ms setb LCD_e mov PortB,#38h ;func set + enable to LCD clrb LCD_e call Wait60ms setb LCD_e mov PortB,#28h ;4bit_mode_instruction + enable clrb LCD_e ;The busy flag is readable after this. call Wait60ms mov temp2,#01h ;Clear and reset cursor call Busy_Check clrb LCD_rs call SendChar mov temp2,#06h ;increment no_shift mode call Busy_Check clrb LCD_rs call SendChar mov temp2,#0Ch ;Display On call Busy_Check clrb LCD_rs call SendChar ;Send initial 10,000.00 display to LCD call Mult call Wait ;allow AD9850 clock time to start call Wait call Write_Data ;side effect reset power down bits of AD9850 call Write_Data ;load the initial display value ;Get the power on encoder value mov temp2,PortA ;get encoder value mov W,#03h and W,temp2 ;isolate direction bits mov old,W ;save initial values ;**************************************************************************** ;Check encoder ; PortA.0 shaft encoder output A ; PortA.1 shaft encoder output B ; PortA.2 shaft encoder push button switch ; Timing checks assume a 4MHz cpu clock and that the encoder ; has 32 detents Chk_encoder inc count1 cjb count1,#75h,ce ; check encoder loop takes 17us inc count2 clr count1 ; 75h * 17us = 2ms ce mov temp1,PortA ;get encoder value mov temp2,temp1 ;double latch the input mov W,#03h and W,temp2 ;isolate direction bits mov new,W ;save the direction bits xor W,old jz Chk_encoder ;keep polling if Encoder is unchanged mov FSR,#14 ;pointer set to 100KHz digit jnb temp2.2,:go ;if pb switch is closed (active low) mov FSR,#18 ;pointer set to 10's cjae count2,#0Fh,:go ;if speed < 1.0RPM mov FSR,#17 ;pointer set to 100's cjae count2,#05h,:go ;if 1.0RPM < Speed < 3.0RPM mov FSR,#16 ;pointer set to 1K ;if Speed > 3.0RPM :go clr count2 ;add time to write LCD etc. to count1 mov count1,#46h ;(which is about 1.2ms) clc ;clear carry bit before doing the rotate rl old xor old,new ;determine direction jb old.1,ce_up call BCD_DEC ;calculated new LCD display call Mult ;calculate new AD9850 control word jmp ce_write ce_up call BCD_INC ;calculated new LCD display call Mult ;calculate new AD9850 control word ce_write call Write_Data mov old,new jmp Chk_encoder ;continue polling the encoder