; ; CI-V DDS Controller ; ; Version 1.0 - March 2007 ; ; Alex Krist - KR1ST ; ; This software acts as a Icom CI-V driver between a serial port and the DDS-60 ; using the Serial DDS board. It will accept CI-V commands from the serial port ; and converts them into control words to change the frequency of the DDS. The ; commands supported are "00", "05" and "03". "00" and "05" set the frequency ; of the DDS, and "00" reads the frequency of the DDS. The difference between ; commands "00" and "05" is that the former does not get achknowleged by the ; controller, while the latter will send an "OK" response to the computer. ; The address of the controller is "5C" by default, which is the address of ; a IC-756Pro. This means that the PC software communicating with the controller ; should be set to work with a Icom IC-756Pro. include "P16f628.inc" processor PIC16F628 __config _XT_OSC & _WDT_OFF & _PWRTE_OFF & _BODEN_OFF & _MCLRE_OFF & _LVP_OFF radix dec errorlevel -302 ; suppress message 302 from list file ; Oscillator frequency currently 180 MHz (6 x 30 MHz). ref_osc_3 equ 0x17 ; MSB ref_osc_2 equ 0xDC ref_osc_1 equ 0x65 ref_osc_0 equ 0xDE ; LSB ; Default contains the default startup frequency as a 32 bit integer. default_3 equ 0x00 ; MSB for 14.025 MHz default_2 equ 0xD6 default_1 equ 0x01 default_0 equ 0x28 ; LSB ; I/O Pins, B register bits DDS_clk equ 0x02 ; AD9851 write clock DDS_dat equ 0x03 ; AD9851 serial data input DDS_load equ 0x07 ; Update pin on AD9851 ; Serial Port constants ; ; These are used by the serial comm routines for timing. Note that ; slower speeds may not work well because the delays might be larger ; than 255, requiring the use of the prescalar. _Clkspd equ 1000000 ;external clock / 4 _BaudRate equ 9600 ;desired baud rate _Period equ (_Clkspd/_BaudRate) ;clock cycles / bit _StartRxDelay equ (_Period/2 - 15)/3 ;this is how long to ;wait to get to the ;middle of the start ;bit. This is loops, ;not clock cycles. _BitRxDelay equ 277 - _Period ;this is what to load ;into TMR0 for correct ;interval between bits ;on RX _BitTxDelay equ 285 - _Period ;this is what to load ;into TMR0 for correct ;interval between bits ;on TX _StopTxDelay equ 272 - _Period ;this is what to load ;into TMR0 for correct ;interval betweeen last ;bit and stop bit on TX ; CI-V Constants OurID equ 0x5c ;identifier code of this controller (IC-756Pro radio) CPUID equ 0xE0 ;identifier of caller (CPU) SOF equ 0xFE ;Start Of Frame character EOF equ 0xFD ;End Of Frame character OKResponse equ 0xFB ;OK CI-V response code NGResponse equ 0xFA ;No Good CI-V response code ReadCommand equ 0x03 ;CI-V Read Command character ASCII_offset equ 0x30 ; ASCII offset ; constants for PORTA: _TX equ 1 ;serial output is PORTA,1 ; constants for PORTB: _RX equ 0 ;PORTB,0 receives the serial input ; constants for ascii to binary conversion of input frequency: ; defines for hex equivalent of each of the eight ascii digits to be received: ; billions _Digit9_byte3 equ 0x3B _Digit9_byte2 equ 0x9A _Digit9_byte1 equ 0xCA _Digit9_byte0 equ 0x00 ; 100 millions _Digit8_byte3 equ 0x05 _Digit8_byte2 equ 0xF5 _Digit8_byte1 equ 0xE1 _Digit8_byte0 equ 0x00 ; 10 millions _Digit7_byte2 equ 0x98 _Digit7_byte1 equ 0x96 _Digit7_byte0 equ 0x80 ; millions _Digit6_byte2 equ 0x0F _Digit6_byte1 equ 0x42 _Digit6_byte0 equ 0x40 ; 100 thousands _Digit5_byte2 equ 0x01 _Digit5_byte1 equ 0x86 _Digit5_byte0 equ 0xA0 ; 10 thousands _Digit4_byte1 equ 0x27 _Digit4_byte0 equ 0x10 ; thousands _Digit3_byte1 equ 0x03 _Digit3_byte0 equ 0xE8 ; hundreds _Digit2_byte0 equ 0x64 ; tens _Digit1_byte0 equ 0x0A ; ones _Digit0_byte0 equ 0x01 ; Defines for the EEPROM memory locations: _EEPROM_Freq equ 0x00 _EEPROM_Start equ 0x04 _EEPROM_End equ 0x08 _EEPROM_Step equ 0x0C ; Variables in general purpose register space CBLOCK 0x20 ; Start Data Block freq_0 ; Display frequency (hex) freq_1 ; (4 bytes) freq_2 freq_3 AD9851_0 ; AD9851 control word AD9851_1 ; (5 bytes) AD9851_2 AD9851_3 AD9851_4 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 timer1 ; Used in delay routines timer2 ; " count ; loop counter (gets reused) BitCount ;number of bits left to send or ;receive, not including start & stop ;bits RXChar ;received character while being received RXBuff ;most recently received character TXChar ;character to transmit SerialReg ;status register: ;bit 0: on if character has been ; received ;bit 1: on if busy with RX/TX ;bit 2: on if sending, off if receiving ;bit 3: on if next bit to send is stop bit WSave ;copy of W register SSave ;copy of the Status register Digit_val ; the value of the current frequency digit being processed ; (not the ASCII value) Add_0 ; a four-byte number to add using the Add_DWord subroutine Add_1 ; (Add_3 is high byte) Add_2 Add_3 ASCII_Buf:10 ; ten-character ASCII buffer BCD_value ; to store a BCD value ENDC ; End of Data Block ; The 16F628 resets to 0x00. * ORG 0x0000 reset_entry movlw h'07' movwf CMCON ; Turn off Comparator goto start ; Jump around the band table to main program ; The Interrupt vector is at 0x04. * org 0x04 ; Serial Communication Routines ; ; The serial comm routines generate 1 start bit, 8 data bits, 1 stop bit, ; no parity. The baud rate is determined by the delay programmed ; into the onboard timer. The sending and receiving is interrupt driven, ; meaning other tasks can be carried on while the characters are being ; sent and received. ; Main Interrupt Routine ; Save the W and STATUS registers: Int movwf WSave swapf STATUS,W ;use swapf to prevent any movwf SSave ;status flags from being changed ; Check first for a timer overflow interrupt. btfsc INTCON,T0IE goto DoBit ;we're in the middle of sending or ;receiving ; If not a timer overflow interrupt, check for external interrupt: btfsc INTCON,INTE ;RB0 is our receive line and it goto StartRX ;generates an interrupt on a high- ;to-low transition ; Restore the W and STATUS registers: Restore swapf SSave,W movwf STATUS swapf WSave,F swapf WSave,W retfie ; Subroutine SerSetup SerSetup ; set up the option register for internal counting, WDT disabled, ; no prescaler. clrf TMR0 bsf STATUS,RP0 clrwdt ;set bits in OPTION_REG to movlw b'10001000' ;enable internal clock counting, movwf OPTION_REG ;disable watchdog timer. bcf STATUS,RP0 ;switch to bank 0 ; set the output line to idle (high) bsf PORTA,_TX ;set the output line to idle(high) state ; enable the external interrupt via RB0 movlw b'10010000' ;set bits in INTCON to enable movwf INTCON ;external interrupt ; initialize the SerialReg: clrf SerialReg return ; Subroutine StartRX ; ; This subroutine is called by the main interrupt routine when an ; external interrupt on RB0 occurs. This means we're receiving the ; start bit for a character. We want to enable the external TMR0 ; interrupt and prepare to receive the character. StartRX ; wait halfway through the bit to see if it's real: bcf INTCON,INTF ;clear the interrupt movlw _StartRxDelay movwf BitCount ;this is the 15th instruction since ;the interrupt. Note--we're using ;BitCount for this loop purely for ;convenience. Usually it's used to ;actually count the bits we TX/RX. RXWait decfsz BitCount,F ;this loop takes 3 times the initial goto RXWait ;value of BitCount clock cycles ; now we should be at the middle of the start bit. Is the input still ; low? If not, goto Restore and ignore this interrupt. btfsc PORTB,_RX goto Restore ; if we get to here it must really be the start bit. Load TMR0, ; disable the external interrupt, and enable the TMR0 interrupt. ; load up the appropriate delay to get us to the middle of the ; first bit: movlw _BitRxDelay movwf TMR0 ;4 cycles from read of PORTB movlw b'00100000' movwf INTCON ; set the SerialReg to indicate that the routines are busy getting ; a character: movlw b'00000010' movwf SerialReg ; initialize BitCount: movlw 8 movwf BitCount ; okay, now we return. goto Restore ; DoBit ; ; sends or receives the next bit. Bits are sent/received from least ; to most significant bit. DoBit ; clear the TMR0 overflow interrupt flag: bcf INTCON,T0IF ; Are we receiving? btfsc SerialReg,2 goto Sending ; check to see if we're receiving the stop bit: movf BitCount,F btfsc STATUS,Z goto GetStopBit ; if we get to here, we're in the middle of receiving. Get the next ; bit: (16 cycles to get to the next instruction from the start of ; the interrupt). rrf PORTB,W ;rrf PORTB into W. This sets ;the carry bit if RB0 was high. rrf RXChar,F ;doing a rrf on RXChar brings ;in the carry bit to the MSB. ; Decrement the bit counter. decf BitCount,F ; reload TMR0 for the next interrupt, and ; go to the end of the interrupt routine. movlw _BitRxDelay movwf TMR0 ;21 cycles from start of interrupt goto Restore ; if we get to here it's because we need to check for the stop bit. GetStopBit btfss PORTB,_RX ;is the RX line low? If so, it's not goto Done ;the stop bit. Otherwise, set the movlw b'00000001' ;SerialReg to show a character has movwf SerialReg ;been received movf RXChar,W ;copy the received character to RXBuff movwf RXBuff goto Done ; We got here because we're sending. ; check to see if we're finished sending the stop bit: Sending btfsc SerialReg,3 goto Done ; check to see if we need to send the stop bit: movf BitCount,F btfsc STATUS,Z ;18th cycle goto SendStopBit ; if we get to here, we're in the middle of sending. Send the next ; bit: (16 cycles to get to the next instruction from the start of ; the interrupt). rrf TXChar,F ;doing rrf on TXChar puts the btfss STATUS,C ;least significant bit in the goto SendZero ;carry flag. nop bsf PORTA,_TX ;if carry is set, send a one. goto EndDoBit ;PORTA,_TX is set on the 24th cycle SendZero bcf PORTA,_TX ;otherwise, send a zero. (24th cycle) nop ;nop's are for taking the same time nop ;to get to reloading TMR0 as for when ;a one is sent. ; Decrement the bit counter. EndDoBit decf BitCount,F ; reload TMR0 for the next interrupt, and ; go to the end of the interrupt routine. movlw _BitTxDelay movwf TMR0 ;29th cycle goto Restore ; Here we need to send the stop bit, turn off the TMR0 interrupt, ; turn on the external interrupt, and set the SerStatus register ; flags appropriately. SendStopBit nop nop nop bsf PORTA,_TX ;no. Send the stop bit. (24th cycle) bsf SerialReg,3 ;set the "sending stop bit" flag ; reload TMR0 for the next interrupt, and ; go to the end of the interrupt routine. movlw _StopTxDelay movwf TMR0 ;27th cycle goto Restore ; we're completely done sending or receiving. Clean up. Done movlw b'00010000' ;set bits in INTCON to enable movwf INTCON ;external interrupt movlw b'00000001' andwf SerialReg,F ;clear the busy bits in SerialReg goto Restore ; Subroutine SendChar ; ; This is not called by the interrupt handler. Rather, it activates ; the interrupts needed to send it. Put the character to be sent in ; the TXChar file register before calling this subroutine. ; SendChar ; send the start bit: bcf PORTA,_TX ; set the SerStatus to indicate that the routines are busy sending ; a character: movlw b'00000110' movwf SerialReg ; load up TMR0 so it overflows at the right time. nop ;for timing movlw _BitTxDelay movwf TMR0 ;5th cycle after write to PORTA ; clear the external interrupt flag, disable the external interrupt, ; and enable the TMR0 interrupt. movlw b'10100000' movwf INTCON ; set the BitCount for the eight bits to send: movlw 8 movwf BitCount return ; GetAChar GetAChar call Idle btfss SerialReg,0 ;wait for a character to be received goto GetAChar bcf SerialReg,0 return ; SendAChar SendAChar call SendChar WaitToFinish call Idle btfsc SerialReg,1 ;wait for the character to be sent goto WaitToFinish return ; ParseCIVCommand ; ; This is a table jump to CIV commands identified by the received ; frame. ParseCIVCommand addwf PCL,F goto SetFrequencyNoResponse goto NoWhere goto NoWhere goto ReadOperatingFrequency goto NoWhere goto SetFrequency ; Sub_DWord ; subtracts Add_* from freq_*. Stores the result in freq_*. Does ; subtraction using two's complement addition. Sub_DWord comf Add_0,F ;complement the four bytes comf Add_1,F comf Add_2,F comf Add_3,F incf Add_0,F ;add one btfss STATUS,Z ;handle carry in byte 0 goto Add_DWord incf Add_1,F ;handle carry in byte 1 btfss STATUS,Z goto Add_DWord ;handle carry in byte 2 incf Add_2,F btfss STATUS,Z ;handle carry in byte 3 goto Add_DWord incf Add_3,F ; falls through to Add_DWord now to finish the subtraction ; Add_DWord ; adds freq_* to Add_*. Stores the result in freq_*. Add_DWord movf freq_3,W addwf Add_3,W movwf freq_3 movf freq_2,W addwf Add_2,W btfsc STATUS,C call inc_3 movwf freq_2 movf freq_1,W addwf Add_1,W btfsc STATUS,C call inc_2 movwf freq_1 movf freq_0,W addwf Add_0,W btfsc STATUS,C call inc_1 movwf freq_0 return ; inc_1, inc_2, and inc_3 provide a convenient way to increment on carries ; during the add above, in case multiple carries occur on an add inc_1 incf freq_1,F btfss STATUS,Z return inc_2 incf freq_2,F btfss STATUS,Z return inc_3 incf freq_3,F return ; Loop_Add ; ; Loop_Add is called by ASCII_to_Bin to repeatedly add one to the current ; digit. It assumes that Digit_val has been loaded with the ASCII character ; for the digit and Add_3, Add_2, Add_1, Add_0 are the value to add to the ; buffer repeatedly. For example, if Digit_val is '5' and we're working on the ; tens digit, ten is added to the buffer five times. Loop_Add skips the add ; process if Digit_val is '0'. Loop_Add movlw '0' ;skip if the digit is zero subwf Digit_val,F btfsc STATUS,Z return Loop_Add_Start call Add_DWord decfsz Digit_val,F goto Loop_Add_Start return ; ASCII_to_Bin ; ; ASCII_to_Bin converts an 8-character ASCII numeric string to a binary value ; and stores it in buf_3, buf_2, buf_1, buf_0 (buf_3 is high byte). ASCII_to_Bin ; Billions movf ASCII_Buf,W movwf Digit_val movlw _Digit9_byte3 movwf Add_3 movlw _Digit9_byte2 movwf Add_2 movlw _Digit9_byte1 movwf Add_1 movlw _Digit9_byte0 movwf Add_0 call Loop_Add ; Hundred Millions movf ASCII_Buf+1,W movwf Digit_val movlw _Digit8_byte3 movwf Add_3 movlw _Digit8_byte2 movwf Add_2 movlw _Digit8_byte1 movwf Add_1 movlw _Digit8_byte0 movwf Add_0 call Loop_Add ; Ten Millions movf ASCII_Buf+2,W movwf Digit_val clrf Add_3 movlw _Digit7_byte2 movwf Add_2 movlw _Digit7_byte1 movwf Add_1 movlw _Digit7_byte0 movwf Add_0 call Loop_Add ; Millions movf ASCII_Buf+3,W movwf Digit_val movlw _Digit6_byte2 movwf Add_2 movlw _Digit6_byte1 movwf Add_1 movlw _Digit6_byte0 movwf Add_0 call Loop_Add ; Hundred Thousands movf ASCII_Buf+4,W movwf Digit_val movlw _Digit5_byte2 movwf Add_2 movlw _Digit5_byte1 movwf Add_1 movlw _Digit5_byte0 movwf Add_0 call Loop_Add ; Ten Thousands movf ASCII_Buf+5,W movwf Digit_val clrf Add_2 movlw _Digit4_byte1 movwf Add_1 movlw _Digit4_byte0 movwf Add_0 call Loop_Add ; Thousands movf ASCII_Buf+6,W movwf Digit_val movlw _Digit3_byte1 movwf Add_1 movlw _Digit3_byte0 movwf Add_0 call Loop_Add ; Hundreds movf ASCII_Buf+7,W movwf Digit_val clrf Add_1 movlw _Digit2_byte0 movwf Add_0 call Loop_Add ; Tens movf ASCII_Buf+8,W movwf Digit_val movlw _Digit1_byte0 movwf Add_0 call Loop_Add ; Ones movf ASCII_Buf+9,W movwf Digit_val movlw _Digit0_byte0 movwf Add_0 call Loop_Add return ; Loop_Subtract ; ; used by Bin_To_ASCII ; continues to subtract the contents of Add_* from freq_* until ; an underflow is detected, then adds Add_* back once to reverse ; the underflow. For each successful subtraction, increments ; INDF (which should point to a digit in ASCII_Buf). Loop_Subtract movlw '0' movwf INDF ; make the first subtraction. After this call, Add_* ; contains the two's complement of the original entry. ; Thus, subsequent subtractions (in the loop below) ; should be performed using Add_DWord, since adding the ; two's complement is the same as subtracting the original ; value and this saves us the trouble of reloading the ; original value. call Sub_DWord Loop_Subtract_Loop btfsc freq_3,7 ; test for negative result goto Loop_Subtract_End ; jump out of the loop if negative incf INDF,F call Add_DWord ;see explanation above for why ;we call Add_DWord and not Sub_DWord goto Loop_Subtract_Loop Loop_Subtract_End ; add Add_* back to freq_* to make it positive again. Use Sub_DWord ; since Add_* contains the two's complement of the value to add back. call Sub_DWord return ; Bin_To_ASCII ; ; takes the value in freq_*, converts it to ASCII, and stores it ; in ASCII_Buf. NOTE: freq_* contents get blown away in the process. Bin_To_ASCII ; Billions movlw ASCII_Buf movwf FSR movlw _Digit9_byte3 movwf Add_3 movlw _Digit9_byte2 movwf Add_2 movlw _Digit9_byte1 movwf Add_1 movlw _Digit9_byte0 movwf Add_0 call Loop_Subtract ; Hundred Millions ;movlw ASCII_Buf ;movwf FSR incf FSR,F movlw _Digit8_byte3 movwf Add_3 movlw _Digit8_byte2 movwf Add_2 movlw _Digit8_byte1 movwf Add_1 movlw _Digit8_byte0 movwf Add_0 call Loop_Subtract ; Ten Millions ;movlw ASCII_Buf + 2 ;movwf FSR incf FSR,F clrf Add_3 movlw _Digit7_byte2 movwf Add_2 movlw _Digit7_byte1 movwf Add_1 movlw _Digit7_byte0 movwf Add_0 call Loop_Subtract ; Millions incf FSR,F clrf Add_3 movlw _Digit6_byte2 movwf Add_2 movlw _Digit6_byte1 movwf Add_1 movlw _Digit6_byte0 movwf Add_0 call Loop_Subtract ; Hundred Thousands incf FSR,F clrf Add_3 movlw _Digit5_byte2 movwf Add_2 movlw _Digit5_byte1 movwf Add_1 movlw _Digit5_byte0 movwf Add_0 call Loop_Subtract ; Ten Thousands incf FSR,F clrf Add_3 clrf Add_2 movlw _Digit4_byte1 movwf Add_1 movlw _Digit4_byte0 movwf Add_0 call Loop_Subtract ; Thousands incf FSR,F clrf Add_3 clrf Add_2 movlw _Digit3_byte1 movwf Add_1 movlw _Digit3_byte0 movwf Add_0 call Loop_Subtract ; Hundreds incf FSR,F clrf Add_3 clrf Add_2 clrf Add_1 movlw _Digit2_byte0 movwf Add_0 call Loop_Subtract ; Tens incf FSR,F clrf Add_3 clrf Add_2 clrf Add_1 movlw _Digit1_byte0 movwf Add_0 call Loop_Subtract ; Ones incf FSR,F clrf Add_3 clrf Add_2 clrf Add_1 movlw _Digit0_byte0 movwf Add_0 call Loop_Subtract return ; GetFrequencyFromASCII ; ; GetFrequencyFromASCII converts the character string to binary and stores it in ; freq_* GetFrequencyFromASCII clrf freq_0 clrf freq_1 clrf freq_2 clrf freq_3 call ASCII_to_Bin return ; Idle ; ; Idle should be called whenever the chip is waiting for something ; to happen (waiting for a character to be sent or received, for ; example). Currently, Idle doesn't do anything. Idle return ; get_char waits for a serial character to arrive from the controlling PC ; and then echos it. get_char btfss SerialReg,0 ;has character been received? goto get_char ;no, loop again bcf SerialReg,0 ;got a char, so clear the flag movf RXBuff,W ;move the rx char into W movwf TXChar call SendAChar ;echo the char movf RXBuff,w return ; calc_dds_word ; ; 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 AD9851_3 ... AD9851_0. calc_dds_word clrf AD9851_0 ; Clear the AD9851 control word bytes clrf AD9851_1 ; clrf AD9851_2 ; clrf AD9851_3 ; clrf AD9851_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,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 AD9851_1,f ; and add it in to total btfss STATUS,C ; Does this addition result in a carry? goto add7 ; No, continue with next freq term incfsz AD9851_2,f ; Yes, add one and check for another carry goto add7 ; No, continue with next freq term incfsz AD9851_3,f ; Yes, add one and check for another carry goto add7 ; No, continue with next freq term incf AD9851_4,f ; Yes, add one and continue add7 movf freq_1,w ; Use the freq_1 term addwf AD9851_2,f ; Add freq term to total in correct position btfss STATUS,C ; Does this addition result in a carry? goto add8 ; No, continue with next freq term incfsz AD9851_3,f ; Yes, add one and check for another carry goto add8 ; No, continue with next freq term incf AD9851_4,f ; Yes, add one and continue add8 movf freq_2,w ; Use the freq_2 term addwf AD9851_3,f ; Add freq term to total in correct position btfss STATUS,C ; Does this addition result in a carry? goto add9 ; No, continue with next freq term incf AD9851_4,f ; Yes, add one and continue add9 movf freq_3,w ; Use the freq_3 term addwf AD9851_4,f ; Add freq term to total in correct position noAdd rrf AD9851_4,f ; Shift next multiplier bit into position rrf AD9851_3,f ; Rotate bits to right from byte to byte rrf AD9851_2,f ; rrf AD9851_1,f ; rrf AD9851_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 movlw 0x01 ; Set the 6x reference oscilator bit movwf AD9851_4 ; return ; Done. ; send_dds_word ; ; Purpose: This routine sends the AD9851 control word to the DDS chip ; using a serial data transfer. ; ; Input: AD9851_4 ... AD9851_0 ; ; Output: The DDS chip register is updated. ; send_dds_word movlw AD9851_0 ; Point FSR at AD9851 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,C ; Was it zero? goto send0 ; Yes, send zero bsf PORTB,DDS_dat ; No, send one bsf PORTB,DDS_clk ; Toggle write clock bcf PORTB,DDS_clk ; goto break ; send0 bcf PORTB,DDS_dat ; Send zero bsf PORTB,DDS_clk ; Toggle write clock bcf PORTB,DDS_clk ; 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 AD9851_4+1 ; Next byte (past the end) subwf FSR,w ; btfss STATUS,C ; goto next_byte ; bsf PORTB,DDS_load ; Send load signal to the AD9851 bcf PORTB,DDS_load ; return ; ; wait ; ; Purpose: Wait for a specified number of milliseconds. ; ; Entry point wait_a_sec: Wait for 1 second ; Entry point wait_256ms: Wait for 256 msec ; 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_a_sec_and_a_half ; ****** Entry point ****** call wait_256ms ; call wait_256ms ; call wait_256ms ; call wait_256ms ; call wait_256ms ; call wait_256ms ; return wait_a_sec ; ****** Entry point ****** call wait_256ms ; call wait_256ms ; call wait_256ms ; call wait_256ms ; return wait_256ms ; ****** Entry point ****** call wait_128ms ; call wait_128ms ; return 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 ; Start of the program ; start clrf INTCON ; No interrupts ... for now bsf STATUS,RP0 ; Switch to bank 1 bcf 0x01,7 ; Enable weak pullups movlw 0xF5 ; Setup PortA movwf TRISA ; Set port A to all Inputs except RA1 and RA3 movlw 0x01 ; Setup PORTB movwf TRISB ; Set port B to all outputs except RB0 bcf STATUS,RP0 ; Switch back to bank 0 movlw 0x00 ; clear out the buffer in the 9851 movwf AD9851_0 ; to make sure it is a defined state movlw 0x00 movwf AD9851_1 movlw 0x00 movwf AD9851_2 movlw 0x00 movwf AD9851_3 movlw 0x00 movwf AD9851_4 call send_dds_word ; ; Set the power on frequency to the defined value (14.025 MHz). ; movlw ref_osc_3 movwf osc_3 movlw ref_osc_2 movwf osc_2 movlw ref_osc_1 movwf osc_1 movlw ref_osc_0 movwf osc_0 movlw freq_0 call Get_Frequency ; get frequency from EEPROM and into freq_* call calc_dds_word ; Convert to delta value call send_dds_word ; Send the power-on frequency to the DDS call send_dds_word ; Send the power-on frequency to the DDS (to be sure) call wait_128ms ; a short delay in case the oscillator is slow ; getting up to speed call SerSetup ; set up serial comm routines & int. MainLoop call GetAChar ; get char from user ; is it a Start Of Frame charachter? movlw SOF subwf RXBuff,W btfss STATUS,Z goto MainLoop ; received characer was not SOF call GetAChar ; see if we find the second SOF character movlw SOF subwf RXBuff,W btfss STATUS,Z goto MainLoop ; received characer was not second SOF character ; at this point we are receiving what appears to be a valid frame. ; now let's look and see if the frame is meant for us and if it came ; from a CPU. call GetAChar ; get char from CPU ; is it for us? movlw OurID subwf RXBuff,W btfss STATUS,Z goto MainLoop ; this frame is not for us call GetAChar ; get char from CPU ; is it sent by the CPU? movlw CPUID subwf RXBuff,W btfss STATUS,Z goto MainLoop ; not sent by CPU, and we only listen to that ; so far we have started to receive a frame that is indeed meant from us and it ; came from the CPU (PC). now let's see what the the CPU wants from us. call GetAChar ; get the command character from CPU ;movlw 6 ; make sure the command code is < 6 ;subwf RXBuff,W ;btfsc STATUS,C ;Carry bit will be clear if result is negative ;goto MainLoop ;not negative -- out of range; go wait for new frame. movf RXBuff,W call ParseCIVCommand goto MainLoop ; we're done, so let's wait for the next command ; The following subs handle the various CIV commands: ; SetFrequencyNoResponse ; sets the DDS to the frequency in the CIV frame, but does ; not send a response back SetFrequencyNoResponse ;get the BCD frequency from the CI-V frame call Get_Digits ; convert it to binary and store it in freq_* call GetFrequencyFromASCII ; command the DDS to change frequency: call calc_dds_word call send_dds_word ; save the frequency in EEPROM: call Save_Frequency return ; ReadOperatingFrequency ; ; reads the frequency the DDS is set to and sends it back ; to the CPU in a CIV frame. ReadOperatingFrequency call Send_Output_Frequency return ; SetFrequency ; ; sets the DDS to the frequency in the CIV frame, but does ; send a response back to the CPU SetFrequency ; get the BCD frequency from the CI-V frame call Get_Digits ; convert it to binary and store it in freq_* call GetFrequencyFromASCII ; command the DDS to change frequency: call calc_dds_word call send_dds_word ; save the frequency in EEPROM: call Save_Frequency ; send an OK response back to the CPU call Send_OK_Response return ; NoWhere ; ; the command is not supported, so we're going nowhere NoWhere return ; Send_CRLF ; ; Send_CRLF sends a carriage return followed by a line feed. Send_CRLF movlw 0x0D movwf TXChar call SendAChar movlw 0x0A movwf TXChar call SendAChar return ; Save_Frequency ; ; gets the current frequency from freq_* and stores it in EEPROM Save_Frequency movlw freq_0 movwf FSR movlw _EEPROM_Freq call Write_EEPROM return ; Get_Frequency ; ; Gets the current frequency from EEPROM and stores it in ; address given in W. Get_Frequency movwf FSR movlw _EEPROM_Freq call Read_EEPROM return ; Get_Digits ; ; Get_Digits gets up to eight digits from the serial port. Get_Digits ; prefill ASCII_Buf with 0s movlw 10 movwf count movlw ASCII_Buf movwf FSR Prefill_Buf_Loop clrf INDF incf FSR,F decfsz count,F goto Prefill_Buf_Loop ; count will keep track of space remaining in buffer movlw 10 movwf count movlw ASCII_Buf + 9 movwf FSR Get_Digits_Loop call GetAChar ; is it an End Of Frame charachter? movlw EOF subwf RXBuff,W btfsc STATUS,Z goto EndOfFrame ;safe BCD value movfw RXBuff movwf BCD_value ;convert lower nibble to ascii andlw 0x0f ; clear bits 4-7 addlw ASCII_offset ; add 30h to make ascii ;movwf RXBuff ;check to see if there's room in the buffer: movf count,F ; will set the Z flag if zero btfsc STATUS,Z goto Get_Digits_Loop ; there's room in the buffer. Store it. ;movf RXBuff,W movwf INDF decf FSR,F decf count,F ;swap the nibbles of the BCD value and store in W swapf BCD_value, W ;convert higher nibble to ascii andlw 0x0f ; clear bits 4-7 addlw ASCII_offset ; add 30h to make ascii ;movwf RXBuff ;check to see if there's room in the buffer: movf count,F ; will set the Z flag if zero btfsc STATUS,Z goto Get_Digits_Loop ; there's room in the buffer. Store it. ;movf RXBuff,W movwf INDF decf FSR,F decf count,F goto Get_Digits_Loop EndOfFrame ;got an EOF character. Stop getting digits. return ;not full. Loop to move the characters to the end of the buffer ; Send_Output_Frequency ; ; sends the contents of ASCII_Buf over the serial port in a CI-V frame ; This happens as a result of an inquiring CI-V command frame. Send_Output_Frequency ; get the frequency from the EEPROM movlw freq_0 call Get_Frequency ; convert to ASCII call Bin_To_ASCII ; send two SOF (Start Of Frame) characters movlw SOF movwf TXChar call SendAChar movlw SOF movwf TXChar call SendAChar ; send CPUID as to-address movlw CPUID movwf TXChar call SendAChar ; send Our ID as from-address movlw OurID movwf TXChar call SendAChar ; send Read_Command character movlw ReadCommand movwf TXChar call SendAChar ; now send the ascii buffer in BCD format movlw 5 movwf count movlw ASCII_Buf + 9 movwf FSR ; now send the frequency in BCD format Loop_Output_ASCII_Buffer movf INDF,W ; load first char of frequency in W movwf TXChar movlw ASCII_offset subwf TXChar,F ; subtract 30h to find BCD value swapf TXChar,F ; swap the nibbles of the BCD value decf FSR,F movf INDF,W ; get next char of frequency addwf TXChar,F ; add to previous BCD value and store in W movlw ASCII_offset subwf TXChar,F ; subtract 30h to find BCD value swapf TXChar,F ; swap the nibbles of the BCD value ; TXChar now contains two digits of the frequency in BCD format ;movwf TXChar call SendAChar decf FSR,F decfsz count,F goto Loop_Output_ASCII_Buffer ; send EOF (End Of Frame) character movlw EOF movwf TXChar call SendAChar return ; Send_OK_Response Send_OK_Response ; send two SOF (Start Of Frame) characters movlw SOF movwf TXChar call SendAChar movlw SOF movwf TXChar call SendAChar ; send CPUID as to address movlw CPUID movwf TXChar call SendAChar ; send Our ID as from address movlw OurID movwf TXChar call SendAChar ; send Read_Command character movlw OKResponse movwf TXChar call SendAChar ; send EOF (End Of Frame) character movlw EOF movwf TXChar call SendAChar return ; Send_NoGood_Response Send_NoGood_Response ; send two SOF (Start Of Frame) characters movlw SOF movwf TXChar call SendAChar movlw SOF movwf TXChar call SendAChar ; send CPUID as to address movlw CPUID movwf TXChar call SendAChar ; send Our ID as from address movlw OurID movwf TXChar call SendAChar ; send Read_Command character movlw NGResponse movwf TXChar call SendAChar ; send EOF (End Of Frame) character movlw EOF movwf TXChar call SendAChar return ; Read_EEPROM ; ; gets a four byte word starting at the index given in W, and stores ; it starting at INDF. Read_EEPROM bsf STATUS,RP0 movwf EEADR bcf STATUS,RP0 movlw 4 movwf count Read_EEPROM_Loop bsf STATUS,RP0 bsf EECON1,RD movf EEDATA,W incf EEADR,F movwf INDF incf FSR,F bcf STATUS,RP0 decfsz count,F goto Read_EEPROM_Loop return ; Write_EEPROM ; ; writes a four byte word starting at the index given in W, reading it ; from INDF. Write_EEPROM bsf STATUS,RP0 movwf EEADR bcf STATUS,RP0 movlw 4 movwf count bcf INTCON,GIE Write_EEPROM_Loop bsf STATUS,RP0 movf INDF,W movwf EEDATA clrf EECON1 bsf EECON1,WREN movlw 0x55 movwf EECON2 movlw 0xAA movwf EECON2 bsf EECON1,WR btfsc EECON1,WR ; End of writing test goto $-1 ; Loop until end bcf EECON1,EEIF ; Clear EEIF bit telling end of writing sequence incf EEADR,F bcf STATUS,RP0 bcf PIR1,EEIF ; ditto in PIR1 register incf FSR,F decfsz count,F goto Write_EEPROM_Loop bsf INTCON,GIE return ; initialize EEPROM to startup values: org 0x2100 ; currently-set values for frequency and scanning de 0x28, 0x01, 0xD6, 0x00 ; current frequency (lo to hi byte) de 0x80, 0x9F, 0xD5, 0x00 ; lower band edge for scan de 0xB0, 0xF6, 0xDA, 0x00 ; upper band edge for scan de 0x64, 0x00, 0x00, 0x00 ; step size for scan & step END