;*************************************************************************** ; DDS VFO ; (on HC908 Daughtercard in Digital Breadboard) ; ; Revised 7/31/2002 ; ; George Heron, N2APB ; 2419 Feather Mae Ct. ; Forest Hill, MD 21050 ; email: n2apb@amsat.org ; ; This source code runs on the Digital QRP Breadboard, as chronicled in the ; pages of QRP Quarterly since the October 2001 issue. It represents an ongoing ; effort to provide a flexible microcomputing platform for ham radio applications. ; For complete documentation and design details, see the web pages devoted for this ; project at http://www.njqrp.org/digitalhomebrewing/breadboard/breadboard.html. ; ; This code is intended to run on a Motorola 68HC908AB32 microcontroller and is ; best assembled within the Integrated Development Environment editor, provided free ; by Motorola as part of their HC908 development tool suite. ; ; Revision History: ; ---------------- ; v1.0 1/02/2002 Initial version of the DigBB software, providing basic I/O tests. ; v1.1 4/05/2002 Adds the Digital Voltmeter application routines ; v1.3 7/31/2002 Adds the DDS control software, forming a basis for an antenna analyzer ; ;****************************************************************************** ; Copyright 2002 by G. Heron, N2APB. All rights reserved. For personal, non-profit use only. ;****************************************************************************** RAMStart equ $0050 RomStart equ $8000 VectorStart equ $FFD0 cr equ $0D lf equ $0A ;Port A pin usage LCD_pwr equ 7 ;LCD power = bit 7 of porta LCD_e equ 3 ;LCD enable = bit 3 of porta LCD_rw equ 2 ;LCD read/write. 0=write, 1=read LCD_rs equ 1 ;LCD register select. 0=instruction, 1=data ;PortF pin usage DDS_data equ 0 ;DDS data pin DDS_load equ 1 ;DDS load pin DDS_clk equ 2 ;DDS clock pin ENC_a equ 5 ;Encoder-A pin ENC_b equ 4 ;Encoder-B pin ENC_pb equ 3 ;Encoder-Pushbutton pin $Include 'ab_regs.inc' org RamStart temp_long ds 4 temp_word ds 2 temp_byte ds 1 Timeout1 ds 1 ; Allows three timeout routines to be called each of which Timeout2 ds 1 ; can run for up to ~ 1/2 second. Timeout3 ds 1 LCD_timer ds 1 ;counter for timeout check in LCD busy_check LCD_read ds 1 ;temp store for status read in LCD busy_check LCD_char ds 1 ;temp for LCD data written (cmnd2LCD & data2LCD) temp1 ds 1 ;temp store used in write_LCD temp2 ds 1 enc_new ds 1 ;new reading of encoder enc_old ds 1 ;last reading of encoder bit_count ds 1 ;DDS bit counter byte_count ds 1 ;DDS byte counter byte2send ds 1 ;DDS byte to send DDS_w4 ds 1 ;DDS programming register W4 (freq-b7 ... freq-b0) DDS_w3 ds 1 ;DDS programming register W3 (freq-b15 ... freq-b8) DDS_w2 ds 1 ;DDS programming register W2 (freq-b23 ... freq-b16) DDS_w1 ds 1 ;DDS programming register W1 (freq-b31 ... freq-b24) DDS_w0 ds 1 ;DDS programming register W0 (phase-b4 ... control-0) LCD_dat ds 7 ;LCD data display for frequency - MSD (1mHz) to LSD (10 Hz) op1 ds 4 ;operand 1 for add & multiply routine op2 ds 1 ;operand 2 count1 ds 1 count2 ds 1 start_index ds 2 cursor_index ds 2 ;variable for LCD digit address used in setting cursor org RomStart ************************************************************** * Init_Timer - Turns on timer A channel 0 for an Output * * Compare in approximately 2ms. The timer * * interrupt service routine continually sets * * the next interrupt to occur 2ms later. * ************************************************************** Init_Timer: mov #$36,TASC ; Timer A - Cleared + Stopped. ; Clicks once every 64 BUS Cycles ; 77t Clicks ~ 2ms mov #$0,TACH0H ; Set Output Compare to happen 77T clicks mov #77T,TACH0L ; after we start the timer. (~2ms). The ; timer interrupt will set OC for another ~2ms. mov #$54,TASC0 ; Timer A Channel 0 ; Set for Output Compare operation. mov #$06,TASC ; Start the timer rts ************************************************************** * Main_Init - This is the point where code starts executing * * after a RESET. * ************************************************************** Main_Init: rsp clra ;Initialize A,X so that interrupt clrx ;processing doesn't stop with ;uninitialized register warning ;when push A,X on the stack mov #$FF,ddra ;set porta as outputs mov #$07,ddrf ;set up portF ;... lower 3 bits (DDS ctl) as outputs ;... upper bits (Encoder) as inputs mov #1,LCD_dat+0 ; init the display to 10.000.000 MHz mov #0,LCD_dat+1 mov #0,LCD_dat+2 mov #0,LCD_dat+3 mov #0,LCD_dat+4 mov #0,LCD_dat+5 mov #0,LCD_dat+6 clr count1 clr count2 lda portf ;get initial encoder position and #$30 ;isolate the encoder bits eor enc_old ;initialize encoder old value ldhx #1 ;init the digit address for cursor sthx cursor_index bsr Init_Timer clr timeout1 ;init timeouts (0=off) clr timeout2 clr timeout3 cli ;allow interrupts to happen ************************************************************** * PROGRAM MAINLINE * ************************************************************** main: mov #3,timeout1 ; Start an A/D conversion in 3*2ms=6ms ;Display sign-on message jsr LCD_init ldhx #signon_msg jsr disp_msg ;display sign-on message ;Update DDS ; jsr LCD_clear ; jsr mult ;cal new dds control word from LCD_dat_n jsr write_data ;send LCD_dat_n digits to LCD ; jsr set_DDS ;send control word to DDS lda #$0E ;cursor on jsr cmnd2LCD lda #$81 ;cursor to position 1 jsr cmnd2LCD ;Check the Encoder m3: mov portf,temp2 ;buffer the input lda temp2 and #$30 ;isolate the encoder bits sta enc_new eor enc_old beq m3 ;no change, wait in encoder loop bclr 3,enc_old ;change detected ... which way? lda enc_old rola eor enc_new sta enc_old brset 5,enc_old,up ;encoder turned down (ccw) down: brset 3,temp2,dwn1 ldhx cursor_index ;adjust cursor over digit when enc pb depressed decx txa ;index in A cmp #$8 ;at list end? tax bne dwn2 ;no, continue clrx dwn2: sthx cursor_index lda digit_posn,X ;get LCD digit address jsr cmnd2LCD ;put cursor there mov enc_new,enc_old bra m3 dwn1: ldhx cursor_index ;point to digit to decrement jsr bcd_dec ;decrement the BCD digit currently pointed to jsr mult ;calc new frequency jsr write_data ;update LCD jsr set_DDS ;update the DDS chip mov enc_new,enc_old bra m3 ;encoder turned up (cw) up: brset 3,temp2,up1 ldhx cursor_index ;adjust cursor over digit when enc pb depressed incx txa ;index in A cmp #$8 ;at list end? tax bne up2 ;no, continue clrx up2: sthx cursor_index lda digit_posn,X ;get LCD digit address jsr cmnd2LCD ;put cursor there mov enc_new,enc_old bra m3 up1: ldhx cursor_index ;point to digit to decrement jsr bcd_inc ;increment the BCD digit currently pointed to jsr mult ;calc new frequency jsr write_data ;update LCD jsr set_DDS ;update the DDS chip mov enc_new,enc_old bra m3 signon_msg: db 'DigBB1.3',0 digit_posn: db $C0,$c1,$C2,$C3,$C4,$C5,$C6,$C7 ;digits 1MHz to 10Hz (omit 10Mhz) ************************************************************** * LCD Driver * ************************************************************** ;------------------------------------------------------------ ;Display character in A to the LCD. LCD_rw and LCD_rs must be ;set up prior to entry. cmnd2LCD: sta LCD_char ;temp save char to be written bclr LCD_rs,porta ;rs is low for cmnd jsr busy_check ;LCD ready for new data? clr porta mov #$FF,ddra ;setup for all outputs bclr LCD_rw,porta ;change LCD back to Write mode bclr LCD_rs,porta ;ensure that rs is low for cmnd bra write_LCD ;continue data2LCD: sta LCD_char ;temp save char to be written bset LCD_rs,porta ;rs is hi for data jsr busy_check ;LCD ready for new data? clr porta mov #$FF,ddra ;setup for all outputs bclr LCD_rw,porta ;change LCD back to Write mode bset LCD_rs,porta ;ensure that rs is hi for data write_LCD: lda LCD_char ;get the character and #$F0 ;deal with hi nibble sta temp1 lda porta ;get porta image and #$0F ;clear the data nibble ora temp1 ;combine with character hi nibble sta porta bset LCD_e,porta ;strobe enable nop bclr LCD_e,porta lda LCD_char ;get character back again nsa ;get low nibble hi and #$F0 sta temp1 lda porta and #$0F ora temp1 sta porta bset LCD_e,porta nop bclr LCD_e,porta rts ;----------------------------------------------------- ;Busy_check Subroutine ; ;LCD read/write operations are slooooow, this subroutine ;polls the LCD busy flag to determine if previous operations are completed. busy_check: clra mov #$0F,ddra ;tristate the data pins bclr LCD_rs,porta ;set up to read busy flag bset LCD_rw,porta ;set up for Read mov #$FF,LCD_timer ;init timeout counter busy: bset LCD_e,porta ;raise the Enable bit mov porta,LCD_read ;read the byte bclr LCD_e,porta nop nop bset LCD_e,porta ;get next nibble to keep LCD happy nop bclr LCD_e,porta dbnz LCD_timer,busy2 ;timer not zero bra not_busy ;timer=0 so exit anyway busy2: brset 7,LCD_read,busy ;check for busy flag - wait if set not_busy: rts ;------------------------------------------------------------ ; LCD_init ; ; Power on initialization of LCD. ; The LCD controller chip must be equivalent to an Hitachi 44780. LCD_init: jsr Wait64ms bset LCD_e,porta mov #$30,porta ;func set bset LCD_e,porta jsr Wait64ms bclr LCD_e,porta bset LCD_e,porta mov #$30,porta ;func set bset LCD_e,porta jsr Wait32ms bclr LCD_e,porta bset LCD_e,porta mov #$30,porta ;func set bset LCD_e,porta jsr Wait32ms bclr LCD_e,porta lda #$20 ;set 4-bit mode bset LCD_e,porta jsr Wait16ms bclr LCD_e,porta lda #$28 ;1/16 duty cycle, 5x8 matrix jsr cmnd2LCD lda #$08 ;disp off, curs & blank off jsr cmnd2LCD lda #$01 ;clear & reset cursor jsr cmnd2LCD lda #$06 ;cursor moves right, no shift jsr cmnd2LCD lda #$0C ;display on, cursor & blink off jsr cmnd2LCD rts ;***************************************************************************** ; LCD_clear -- clears the LCD display LCD_clear: lda #$01 jsr cmnd2LCD rts ;***************************************************************************** ; DISP_MSG -- displays the message pointed to by H:X disp_msg: lda ,x beq disp_msg_end ;zero ends the string jsr data2LCD incx bra disp_msg disp_msg_end: rts ;**************************************************************************** ; BCD_INC -- BCD increment subroutine ; ; This routine does a binary coded decimal increment to the values in LCD_Dat. ; On entry, the X register contains the offset (0-7) of the LCD_Dat digit to be incremented. ; If the result is over 30MHz the value is reset back to 30MHz. BCD_INC: sthx start_index ;save the starting index (points to selected digit) BCD_I: lda LCD_dat+0 cmp #2 ;is ms digital already at max? beq exit2 ;yes, just exit without incrementing lda LCD_dat,X ;get the digit to increment add #1 sta temp1 add #6 bhcs carry ;the add went past 9 lda temp1 ;else just store char and exit and #$0F sta LCD_dat,X bra exit2 carry: clr LCD_dat,X ;clear the current digit decx ;point to next higher digit tstx ;pointing to ms digit? bne BCD_I ;no, just increment & test again lda LCD_dat,X ;yes, is ms digit already 1 (19.9999MHz) cmp #1 bne BCD_I ;no, increment like normal ldhx #0 ;yes, set display to max value from first digit to start index lda #2 ;max = 20 MHz set_max: sta LCD_dat,X txa ;current pointer now in A cmp start_index+1 ;compare to X reg at saved start_index beq exit2 ;then done, just finished max'ing to point of start index tax ;current pointer back in X incx ;point to next char to the right clra ;clear data for next char max setting bra set_max exit2: rts ;*************************************************************************** ; BCD_DEC -- BCD decrement subroutine ; ; This routine does a binary coded decimal decrement to the values in LCD_Dat. ; On entry the INDEX variable must point to the LCD_Dat digit to be decremented, ; INDEX variable = 0 (10mHz) through 6 (10Hz) BCD_DEC: sthx start_index ;save the starting index (points to selected digit) BCD_d1: lda LCD_dat,X ;get the digit to increment sub #1 bcs carry2 ;borrow occured sta LCD_dat,X ;no borrow, just store & continue bra exit carry2: lda #9 sta LCD_dat,X ;set current digit = 9 decx ;point to next higher digit tstx ;pointing to ms digit? bne BCD_D1 ;no, ripple upward through next digit lda LCD_dat,X ;yes, is ms digit already 0? tsta bne BCD_D1 ;no, decrement it like others ldhx start_index ;else, set the display to min value set_min: ;starting at initial digit & going upward clr LCD_dat,X tstx ;at ms digit? (i.e., pointer X = 0?) beq exit aix #-1 ;point to next higher digit bra set_min exit: rts ;*************************************************************************** ; ADD32 -- 32 bit add ; ; The value in registers OP1 is added to the current 32 bit value in DDS_w4 thru DDS_w0 Add32: lda DDS_w4 ;process LS byte first add OP1+0 sta DDS_w4 bcc add1 inc DDS_w3 bne add1 inc DDS_w2 bne add1 inc DDS_w1 add1: lda DDS_w3 add OP1+1 sta DDS_w3 bcc add2 inc DDS_w2 bne add2 inc DDS_w1 add2: lda DDS_w2 add OP1+2 sta DDS_w2 bcc add3 inc DDS_w1 add3: lda DDS_w1 ;process MS byte add OP1+3 sta DDS_w1 rts ;*************************************************************************** ; WRITE_DATA -- Write contents of LCD_dat+0 through LCD_dat+6 to the LCD ; Write_Data: jsr Busy_Check lda #87h ;LCD position for first Digit jsr cmnd2LCD jsr Busy_check lda LCD_Dat+0 ora #$20 cmp #$20 ;is digit a zero? beq wd2 ;yes, leave A as space char and go display it ora #$10 ;else, turn char to ascii wd2: jsr data2LCD ;Send 10MHz digit jsr Busy_Check lda #$C0 ;Point LCD at Digit #9, (there is a jsr cmnd2LCD ;gap in the LCD address) jsr Busy_check lda LCD_Dat+1 ora #$30 jsr data2LCD ;Send 1MHz digit jsr Busy_check lda #',' jsr data2LCD ;Send a comma jsr Busy_check lda LCD_Dat+2 ora #$30 jsr data2LCD ;Send 100KHz digit jsr Busy_check lda LCD_Dat+3 ora #$30 jsr data2LCD ;Send 10KHz digit jsr Busy_check lda LCD_Dat+4 ora #$30 jsr data2LCD ;Send 1KHz digit jsr Busy_check lda #'.' jsr data2LCD ;Send a period jsr Busy_check lda LCD_Dat+5 ora #$30 jsr data2LCD ;Send 100Hz digit jsr Busy_check lda LCD_Dat+6 ora #$30 jsr data2LCD ;Send 10Hz digit rts ;***************************************************************************** ; MULT -- 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 weight is stored in the DDS_wN registers. Mult clr temp1 clr DDS_w4 clr DDS_w3 clr DDS_w2 clr DDS_w1 clr DDS_w0 m10: mov #$84,OP1+0 mov #$02,OP1+1 clr OP1+2 clr OP1+3 mov LCD_Dat+6,OP2 bra m_go m100: mov #$2A,OP1+0 mov #$19,OP1+1 clr OP1+2 clr OP1+3 mov LCD_Dat+5,OP2 bra m_go m1K: mov #$A9,OP1+0 mov #$FB,OP1+1 clr OP1+2 clr OP1+3 mov LCD_Dat+4,OP2 bra m_go m10K: mov #$9B,OP1+0 mov #$D4,OP1+1 mov #$09,OP1+2 clr OP1+3 mov LCD_Dat+3,OP2 bra m_go m100K: mov #$13,OP1+0 mov #$4E,OP1+1 mov #$62,OP1+2 clr OP1+3 mov LCD_Dat+2,OP2 bra m_go m1M: mov #$C1,OP1+0 mov #$0C,OP1+1 mov #$D7,OP1+2 mov #03,OP1+3 mov LCD_Dat+1,OP2 bra m_go m10M: mov #$90,OP1+0 mov #$7F,OP1+1 mov #$66,OP1+2 mov #$26,OP1+3 mov LCD_Dat+0,OP2 bra m_go m_go: mov #4,temp2 digit_loop: clc ror OP2 bcc noCarry jsr Add32 noCarry: clc rol OP1+0 rol OP1+1 rol OP1+2 rol OP1+3 dbnz temp2,digit_loop inc temp1 lda temp1 cmp #1 beq m100 cmp #2 beq m1K cmp #3 beq m10K cmp #4 beq m100K cmp #5 beq m1M cmp #6 beq m10M rts ;***************************************************************************** ; SET_DDS - This routine sends the 5-byte DDS control word (DDS_w4 through ; DDS_w0, LSB to MSB in each) to the DDS chip by serial data transfer. set_dds: ldhx #DDS_w4 ; point to the start of the DDS programming words mov #$05,byte_count ; we'll process 5 bytes nxtbyt: mov X+,byte2send ; get the DDS word (and increment pointer H:X) mov #$08,bit_count ; we'll process 8 bits nxtbit: ror byte2send ; test next LSB for 0 or 1 bcc send0 ; send a 0 if carry clear send1: bset DDS_data,portf ; else send a one bset DDS_clk,portf ; Toggle write clock bclr DDS_clk,portf bra check send0: bclr DDS_data,portf ; Send zero bset DDS_clk,portf ; Toggle write clock bclr DDS_clk,portf check: dbnz bit_count,nxtbit ; get next DDS programming bit if not done dbnz byte_count,nxtbyt ; get next DDS programming byte if not done bset DDS_load,portf ; else done - send DDS load signal bclr DDS_load,portf rts ;*************************************************************** ; set_10MHz -- Set the DDS to 10 KHz set_10MHz: mov #$01,DDS_w4 ;test DDS programming data mov #$EE,DDS_w3 mov #$09,DDS_w2 mov #$00,DDS_w1 mov #$00,DDS_w0 jsr set_dds ;set the DDS rts ************************************************************** * MESSAGES * ************************************************************** msg1: db 'Ready',0 ;*************************************************************** ; Wait64ms-- Waits here for 32ms wait64ms: mov #32,timeout1 w64a: tst timeout1 bne w64a rts ;*************************************************************** ; Wait32ms-- Waits here for 32ms wait32ms: mov #16,timeout1 w32a: tst timeout1 bne w32a rts ;*************************************************************** ; Wait64ms-- Waits here for 16ms wait16ms: mov #8,timeout1 w16a: tst timeout1 bne w16a rts ;*************************************************************** ; Wait500us -- Waits here for 500us wait500us: lda #$79 w500u: deca nop bne w500u rts ************************************************************** * T_ISR - Timer Interrupt Service Routine. * * after a RESET. * ************************************************************** T_ISR: pshh lda tasc0 and #$7f sta tasc0 ; Clear O.C. Flag ldhx tach0h aix #77T ; Setup another interrupt in ~2ms sthx tach0h pulh Check_t1: tst timeout1 beq check_t2 ; Is Timeout 1 currently active? dec timeout1 ; yes bne check_t2 ; Did it just finish counting down? jsr t1_handler ; Yes - Execute Timeout 1 handler Check_t2: tst timeout2 beq check_t3 ; Is Timeout 2 currently active? dec timeout2 ; yes bne check_t3 ; Did it just finish counting down? jsr t2_handler ; Yes - Execute Timeout 2 handler Check_t3: tst timeout3 beq done_tisr ; Is Timeout 3 currently active? dec timeout3 ; yes bne done_tisr ; Did it just finish counting down? jsr t3_handler ; Yes - Execute Timeout 3 handler done_tisr: rti **************************************************************** * Timeout Handlers - All the user has to do is set one of the * * timeout variables to any number n (1-255) * * and the timeout handler will be executed * * in 2*n milliseconds. Setting the timeout * * variable from within the handler will * * cause a periodic timeout as shown in * * timeout 1. * **************************************************************** t1_handler: nop rts t2_handler: nop rts t3_handler: nop rts ************************************************************** * DUMMY_ISR - Dummy Interrupt Service Routine. * * Just does a return from interrupt. * ************************************************************** dummy_isr: nop rti ; return ************************************************************** * Vectors - Timer Interrupt Service Routine. * * after a RESET. * ************************************************************** org VectorStart dw dummy_isr ; ADC Conversion Complete dw dummy_isr ; Keyboard Vector dw dummy_isr ; SCI Transmit Vector dw dummy_isr ; SCI Receive Vector dw dummy_isr ; SCI Error Vector dw dummy_isr ; RESERVED dw dummy_isr ; RESERVED dw dummy_isr ; TIMB Channel 3 Vector dw dummy_isr ; TIMB Channel 2 Vector dw dummy_isr ; SPI Transmit Vector dw dummy_isr ; SPI Receive Vector dw dummy_isr ; TIMB Overflow Vector dw dummy_isr ; TIMB Channel 1 Vector dw dummy_isr ; TIMB Channel 0 Vector dw dummy_isr ; TIMA Overflow Vector dw dummy_isr ; TIMA Channel 3 Vector dw dummy_isr ; TIMA Channel 2 Vector dw dummy_isr ; TIMA Channel 1 Vector dw t_isr ; TIMA Channel 0 Vector dw dummy_isr ; TIM Overflow Vector dw dummy_isr ; PLL Vector dw dummy_isr ; ~IRQ1 Vector dw dummy_isr ; SWI Vector dw main_init ; Reset Vector