;       DDSPC4.ASM     G4JNT  Aug 2000
;Controls AD9850 DDS with ASCII Hex commands via 19200 Baud serial interface
;D0-D4 bug fixed 2000-09-17

;Monitors state of x4 switch via PORTB change interrupt and generates
;modified control word for Freq * 4 (=0)   (nb. no overflow checking)
;Clock freq info stored for reference.
;~~PC4 version adds precise timing control input with related "T" command

        LIST P=16C84
;               Command Protocol
;               Address byte  ASCII  0-9 / A - F
;                   followed by
;               Qxxxxxxxx [cr]      32 bit frequency word
;               Pxx [cr]            8 bit phase word
;               U                   Update, do not store to EEprom
;               W                   Update and write to EEProm
;               T                   Update on next external timing pulse
;               Y                   Change and store new Address
;               R                   Readback user data info
;               Kxxxxxxxx [cr]      Store new user data info. in EEPROM



        INCLUDE "p16c84.inc"
        cblock 0x0C     
            BitCount
            DelCount,DelCount1,DelCount2   
            Temp
            SD              ;Used in sendback routine
            D0,D1,D2,D3,D4  ;Input data, final frequency / phase word
            R0,R1,R2,R3     ;Holding reg For D0-3  sent to AD9850
                            ;Need to be separated for x4 function
            
            C0,C1,C2,C3,C4  ;Holding reg for clock info
            InstAddr        ;Instrument address
            Counter
            Flags
        endc
        DDSPort         equ     PORTA

        #define         RXD     PORTB , 0   ;Serial data in from PC
        #define         TXD     PORTB , 3   ;Echoed data / status out to PC
        #define         CTS     PORTB , 4   ;Flag etc. echoed back
        #define         ExtTim  PORTB , 1   ;External timing input

        #define         HexFlag Flags , 0

        FData       equ 0       ;DDS Data on port A
        WClk        equ 2
        FQud        equ 1
        Reset       equ 3


;code starts here

        org     0
boot    nop
        clrw
        movwf   INTCON          ;disable interrupts
        goto    startup         ;jump to main code
;.....................................................
ints
        btfss   INTCON , RBIF   ;test for Port B change interrupt
        retfie                  ;any other interrupt
        movlw   d'92'
        movwf   DelCount1
SwBounce                       
        call    Delay
        decfsz  DelCount1       ;approx 10ms debounce delay
        goto    SwBounce
        call    SendDDS         ;polarity  checked in SendDDS

        movlw   "X"
        call    Send232
        movlw   "1"
        btfss   PORTB , 7       ;echo back x4 state   "1" = x4
        movlw   "4"
        call    Send232
        movlw   0x0D
        call    Send232
        movlw   0x0A
        call    Send232

        movf    PORTB , W       ;End mismatch
        bcf     INTCON , RBIF
        retfie               
;.....................................................
startup
        bsf     STATUS,RP0      ;ram page 1
        movlw   b'11100000'     ;
        movwf   PORTA          ;set port A0 as outputs
        movlw   b'10000111'     ;PC interface  B0-RXD, B3-TXD  B4-CTS 
        movwf   PORTB           ;   B1- External timing input    B7-X4 switch
        bcf     OPTION_REG , NOT_RBPU   ;enable pull ups

        bcf     STATUS,RP0      ;ram page 0
        movlw   b'10001000'     ;PortB Change, enable interrupts
        movwf   INTCON          ;GIE + RBIE
        
        
        clrf    DDSPort
        movlw   0x0a            ;initial start up delay to stabilise levels
        movwf   DelCount2
StartDel1                      
        movlw   0
        movwf   DelCount1
StartDel2                       
        call    Delay
        decfsz  DelCount1       ;256 calls to delay ...
        goto    StartDel2
        decfsz  DelCount2       ;delcount2 times
        goto    StartDel1
        call    SendDDS         ;send some rubbish to initialise
        call    Delay

        call    ReadEeprom      ;Want this irrespective of any prog info
        call    SendDDS         ;Boot with stored EEPROM data
        call    Delay

        movlw   0x0A
        call    GetEE
        andlw   b'00001111'     ;Mask to 0 - 15 only
        movwf   InstAddr        ;Instrument address stored in EE loc $0A

        call    SendInit
        call    SendBack        ;Send back start up values
        call    SendClkData

;---------------------------------------------------------------
MainLoop
        call    WaitData        ;Look for address - ASCII digit 0 - 9
        call    DecodeHex       
        subwf   InstAddr , W
        btfss   STATUS , Z
        goto    MainLoop

        call    Ack
        call    WaitData        ;Get first character

        bcf     Temp , 5        ;Upper case conversion
        movf    Temp , W

        sublw   "Q"             
        btfsc   STATUS , Z
        goto    GetQ            ;Freq word, up to 4 hex digits
        
        movf    Temp , W
        sublw   "P"   
        btfsc   STATUS , Z
        goto    GetP            ;Phase word

        movf    Temp , W
        sublw   "U"
        btfsc   STATUS , Z
        goto    Update          ;"U"pdates DDS chip

        movf    Temp , W
        sublw   "W"
        btfsc   STATUS , Z
        goto    WriteIt         ;"W"rites EE and updates DDS chip

        movf    Temp , W
        sublw   "T"
        btfsc   STATUS , Z      ;Updates DDS chip on low-high of external
        goto    TimeWrite       ;timing pulse.


        movf    Temp , W
        sublw   "Y"
        btfsc   STATUS , Z
        goto    ChangeAddr

        movf    Temp , W
        sublw   "R"
        btfsc   STATUS , Z
        goto    GetR

        movf    Temp , W
        sublw   "K"
        btfsc   STATUS , Z
        goto    GetK

        goto    MainLoop        ;Any other character, just ignore
;...............................
GetQ                            ;last 8 bytes sent before CR are used
        clrf    D0
        clrf    D1
        clrf    D2
        clrf    D3
QLoop        
        call    WaitData
        movf    Temp , W
        sublw   0x0D
        btfsc   STATUS , Z      ;look for [cr] to terminate
        goto    GetOutQ
        movf    Temp , W        
        call    DecodeHex       ;generate binary equivalent
        movwf   Temp

        bcf     STATUS , C
        rlf     Temp
        rlf     Temp
        rlf     Temp
        rlf     Temp            ; into 4 MSBs of Temp

        movlw   4
        movwf   Counter
        bcf     STATUS , C      ;Shouldn't be necessary, but.....
ShiftQLoop
        rlf     Temp            
        rlf     D0
        rlf     D1              ;into 32 bit Freq register 4 bits at a time
        rlf     D2
        rlf     D3
        decfsz  Counter         ;any less than 4 chars are right justified
        goto    ShiftQLoop      ;any more, last 4 are taken
        goto    QLoop           ;Loop until a [cr] is received
GetOutQ
        call    SendBack
        goto    MainLoop
;...............................
GetK                            ;last 8 bytes sent before CR are used
        clrf    C0
        clrf    C1
        clrf    C2
        clrf    C3
        clrf    C4
KLoop        
        call    WaitData
        movf    Temp , W
        sublw   0x0D
        btfsc   STATUS , Z      ;look for [cr] to terminate
        goto    GetOutK
        movf    Temp , W        
        call    DecodeHex       ;generate binary equivalent
        movwf   Temp

        bcf     STATUS , C
        rlf     Temp
        rlf     Temp
        rlf     Temp
        rlf     Temp            ; into 4 MSBs of Temp

        movlw   4
        movwf   Counter
        bcf     STATUS , C      ;Shouldn't be necessary, but.....
ShiftKLoop
        rlf     Temp            
        rlf     C0
        rlf     C1              ;into 40 bit clock register 4 bits at a time
        rlf     C2
        rlf     C3
        rlf     C4
        decfsz  Counter         ;any less than 4 chars are right justified
        goto    ShiftKLoop      ;any more, last 4 are taken
        goto    KLoop           ;Loop until a [cr] is received
GetOutK
        call    SendClkData
        call    WriteClockEE
        goto    MainLoop

;...............................
GetP                            ;
        clrf    D4              ;   last byte sent is used
PLoop        
        call    WaitData
        movf    Temp , W
        sublw   0x0D
        btfsc   STATUS , Z      ;look for [cr] to terminate
        goto    GetOutP
        movf    Temp , W        ;generate binary equivalent
        call    DecodeHex
        movwf   Temp
        bcf     STATUS , C
        rlf     Temp
        rlf     Temp
        rlf     Temp
        rlf     Temp            ;initially into 4 MSBs of Temp

        movlw   4
        movwf   Counter
        bcf     STATUS , C
ShiftPLoop                      
        rlf     Temp
        rlf     D4             
        decfsz  Counter
        goto    ShiftPLoop
        goto    PLoop
GetOutP
        movlw   b'11111000'
        andwf   D4         ;Mask out control bits in case these get sent
        call    SendBack
        goto    MainLoop
;...............................
GetR                       ;Readback internal info - not necessarily EEprom
        call    SendClkData
        call    SendBack

        movlw   "A"
        call    Send232
        movlw   "d"
        call    Send232
        movlw   "d"
        call    Send232
        movlw   "r"
        call    Send232
        movlw   "."
        call    Send232
        movlw   " "
        call    Send232


        movf    InstAddr , W    ;Transfer register contents to SD so it
        movwf   SD              ;doesn't get corrupted
        swapf   SD , W          ;Send High nibble first
        andlw   0x0F
        call    HexToAscii
        call    Send232
        movf    SD , W          ;Now do the low nibble
        andlw   0x0F
        call    HexToAscii
        call    Send232

        movlw   0x0D
        call    Send232
        movlw   0x0A
        call    Send232

        goto    MainLoop
;...............................
WriteIt
        call    WriteEeprom
Update
        call    SendDDS
        call    SendBack        ;Echo back register contents to controller
        goto    MainLoop
;...............................
ChangeAddr
        call    WaitData        ;Look for new address - ASCII digit 0 - 9
        call    DecodeHex
        movwf   InstAddr

        movlw   0x0A            ;Save new address
        movwf   EEADR
        movf    InstAddr , W
        movwf   EEDATA
        call    StoreEE

        call    SendInit        ;Echoes device address
        goto    MainLoop
;--------------------------------------
BitDelay                      ;total incl call and return, (3N+5) = 44us
        movlw   d'13'         ;19200 = 52us, -8us used in Tx/Rx routines
        movwf   DelCount      ;baud period delay
LoopBit
        decfsz  DelCount        
        goto    LoopBit
        return

;-------------------------
WaitData
        btfss   RXD           ;look for start transition, low/high = 1/0
        goto    WaitData      ;
        movlw   d'7'          ; ( may need to adjust this)
        movwf   DelCount      ;
        nop
LoopStartBit                  ;this loop 3.N - 1 long = 17
        decfsz  DelCount
        goto    LoopStartBit

        btfss   RXD           ;make sure it's still high, now centre of bit
        goto    WaitData      ;
        movlw   8             ;
        movwf   Counter
        clrf    Temp          ;26us to here from transition = half bit
ByteLoop
        call    BitDelay
        nop
        bcf     STATUS , C     
        btfss   RXD             
        bsf     STATUS , C     
        rrf     Temp            
        decfsz  Counter
        goto    ByteLoop      ;this loop BitDelay + 8
        movf    Temp , w      ;Returns with stop bit+  slack period
        return                ;received byte in Temp and W

;-------------------------
SendBack
        movlw   "Q"
        call    Send232
        movlw   " "
        call    Send232

        movf    D3 , W          ;Transfer register contents to SD so they
        movwf   SD              ;don't get corrupted
        swapf   SD , W          ;Send High nibble first
        andlw   0x0F
        call    HexToAscii
        call    Send232
        movf    SD , W          ;Now do the low nibble
        andlw   0x0F
        call    HexToAscii
        call    Send232

        movf    D2 , W         
        movwf   SD  
        swapf   SD , W  
        andlw   0x0F
        call    HexToAscii
        call    Send232
        movf    SD , W    
        andlw   0x0F
        call    HexToAscii
        call    Send232

        movf    D1 , W         
        movwf   SD  
        swapf   SD , W  
        andlw   0x0F
        call    HexToAscii
        call    Send232
        movf    SD , W    
        andlw   0x0F
        call    HexToAscii
        call    Send232

        movf    D0 , W         
        movwf   SD  
        swapf   SD , W  
        andlw   0x0F
        call    HexToAscii
        call    Send232
        movf    SD , W    
        andlw   0x0F
        call    HexToAscii
        call    Send232
        
        movlw   " "
        call    Send232
        movlw   " "
        call    Send232
        movlw   "P"
        call    Send232
        movlw   " "
        call    Send232

        movf    D4 , W         
        movwf   SD  
        swapf   SD , W  
        andlw   0x0F
        call    HexToAscii
        call    Send232
        movf    SD , W    
        andlw   0x0F
        call    HexToAscii
        call    Send232

        movlw   0x0D            ;Final [CR][LF]
        call    Send232
        movlw   0x0A            ;
        call    Send232

        return
;----------------------------
SendClkData                     ;Send contents of C0-C4
        movlw   "K"
        call    Send232
        movlw   " "
        call    Send232

        movf    C4 , W          ;Transfer register contents to SD so they
        movwf   SD              ;don't get corrupted
        swapf   SD , W          ;Send High nibble first
        andlw   0x0F
        call    HexToAscii
        call    Send232
        movf    SD , W          ;Now do the low nibble
        andlw   0x0F
        call    HexToAscii
        call    Send232

        movf    C3 , W         
        movwf   SD  
        swapf   SD , W  
        andlw   0x0F
        call    HexToAscii
        call    Send232
        movf    SD , W    
        andlw   0x0F
        call    HexToAscii
        call    Send232


        movf    C2 , W         
        movwf   SD  
        swapf   SD , W  
        andlw   0x0F
        call    HexToAscii
        call    Send232
        movf    SD , W    
        andlw   0x0F
        call    HexToAscii
        call    Send232

        movf    C1 , W         
        movwf   SD  
        swapf   SD , W  
        andlw   0x0F
        call    HexToAscii
        call    Send232
        movf    SD , W    
        andlw   0x0F
        call    HexToAscii
        call    Send232

        movf    C0 , W         
        movwf   SD  
        swapf   SD , W  
        andlw   0x0F
        call    HexToAscii
        call    Send232
        movf    SD , W    
        andlw   0x0F
        call    HexToAscii
        call    Send232
        
        movlw   0x0D            ;Final [CR][LF]
        call    Send232
        movlw   0x0A            ;
        call    Send232

        return

;----------------------------
Send232                         ;Enter with data in W, send it to PC
        movwf   Temp
        bsf     TXD             ;start bit
        call    BitDelay        ;44us, need 52 total
        movlw   8
        movwf   Counter
        nop
        nop
        nop
SendDatLoop
        rrf     Temp            ;bit into C
        btfsc   STATUS , C
        bcf     TXD
        btfss   STATUS , C
        bsf     TXD         ;could have 2us jitter depending on 1/0
        call    BitDelay
        decfsz  Counter
        goto    SendDatLoop     ;loop 8 + BitDelay long
        bcf     TXD         ;stop bit
        call    BitDelay
        nop
        nop
        nop
        nop
        nop
        nop
        return

;-------------------------
DecodeHex   ;Single ASCII character to binary with error checking

        ;Untrapped characters remaining ...........
        ;at the moment Upper Case letters G-Z plus characters
        ;  : ; < = > ? @ [ \ ] ^ _ `    will be let through.
        ;Chars 0 - $2F and letters "g" up
        ;are detected.

        bsf     HexFlag         ;Flag for valid hex character
        movwf   Temp            ;
        movlw   0x30
        subwf   Temp            ;subtract $30 and leave in Temp
        btfss   STATUS , C      ;if < $30, result -ve so C=0, error, set=0
        bcf     HexFlag
        
        movf    Temp , W        ;Test for residual from above > d54 ie > "g"
        sublw   d'54'           ;W = 54 - W, if < 54  invalid char, -ve, C=0
        btfss   STATUS , C      ;
        bcf     HexFlag

        movf    Temp , W
        andlw   0x1F            ;mask to 5 bits, resolves upper / lower case
        movwf   Temp            ;if 0 - 9 then Temp is correct, else 7 high

        movf    Temp , W        
        sublw   d'9'            ;W = 9 - W
        btfsc   STATUS , C      ;if -ve, C=0, char > 9, so subtract 7
        goto    ConvDone
        movlw   d'7'
        subwf   Temp            ;
ConvDone
        movf    Temp , W        ;
        andlw   0x0F            ;mask out any rubbish left in

        return                  ;flag set if partly valid 
;-------------------------
HexToAscii
        addlw   0x30
        movwf   Temp
        sublw   0x39
        btfsc   STATUS , C
        goto    AsciiDone
        movlw   7
        addwf   Temp
AsciiDone
        movf    Temp , W
        return

;-------------------------
WriteEeprom                    ;save registers in EEPROM
        movlw   5
        movwf   EEADR
        movf    D0 , W
        movwf   EEDATA
        call    StoreEE
        
        movlw   6
        movwf   EEADR
        movf    D1 , W
        movwf   EEDATA
        call    StoreEE
        
        movlw   7
        movwf   EEADR
        movf    D2 , W
        movwf   EEDATA
        call    StoreEE
        
        movlw   8
        movwf   EEADR
        movf    D3 , W
        movwf   EEDATA
        call    StoreEE
        
        movlw   9
        movwf   EEADR
        movf    D4 , W
        movwf   EEDATA
        call    StoreEE
        return
;-------------------------
WriteClockEE
        movlw   0
        movwf   EEADR
        movf    C0 , W
        movwf   EEDATA
        call    StoreEE
        
        movlw   1
        movwf   EEADR
        movf    C1 , W
        movwf   EEDATA
        call    StoreEE
        
        movlw   2
        movwf   EEADR
        movf    C2 , W
        movwf   EEDATA
        call    StoreEE
        
        movlw   3
        movwf   EEADR
        movf    C3 , W
        movwf   EEDATA
        call    StoreEE

        movlw   4
        movwf   EEADR
        movf    C4 , W
        movwf   EEDATA
        call    StoreEE

        return


;-------------------------
StoreEE                 ;writes the data byte in Data reg to EEprom EEADR
        bsf     STATUS , RP0
        bsf     EECON1 , WREN   ;Enable EEprom writing
        movlw   0x55
        movwf   EECON2
        movlw   0xAA
        movwf   EECON2
        bsf     EECON1 , WR
EEwaitW
        btfss   EECON1 , EEIF
        goto    EEwaitW
        bcf     EECON1 , EEIF       ;has to be cleared manually
        bcf     EECON1 , WR
        bcf     STATUS , RP0
        return
;-------------------------
ReadEeprom                      ;read stored data and update registers
        movlw   5
        call    GetEE
        movwf   D0

        movlw   6
        call    GetEE
        movwf   D1
        
        movlw   7
        call    GetEE
        movwf   D2

        movlw   8
        call    GetEE
        movwf   D3

        movlw   9
        call    GetEE
        movwf   D4


        movlw   0
        call    GetEE
        movwf   C0

        movlw   1
        call    GetEE
        movwf   C1
        
        movlw   2
        call    GetEE
        movwf   C2

        movlw   3
        call    GetEE
        movwf   C3

        movlw   4
        call    GetEE
        movwf   C4



        return
;-------------------------
GetEE
        movwf   EEADR
        bsf     STATUS,RP0      ;ram page 1
        bsf     EECON1 , RD
        bcf     STATUS , RP0    ;ram page 0
        movf    EEDATA , W
        return
;-------------------------
Delay           
        movlw   0x20        ;delay = 5N + 5 = 105us
        movwf   DelCount
DelLoop
        nop
        nop
        decfsz  DelCount
        goto    DelLoop
        return
;-------------------------
SendDDS
        call    DataTo9850          ;Load data into 9850

        bsf     DDSPort , FQud      ;+ve edge of FQud sets 9850 internal reg
        call    Delay
        bcf     DDSPort , FQud
        bcf     DDSPort , FData
        call    Delay               ;total of 43 calls to Delay
                
        return
;-------------------------
TimeWrite                       
        call    DataTo9850      ;Load data into 9850

WaitTim        
        btfss   ExtTim          ;Wait for a high on external input
        goto    WaitTim         ;before updating DDS chip

        bsf     DDSPort , FQud  ;+ve edge of FQud sets 9850 internal reg
        call    Delay           ;approx 3 clocks after external pulse
        bcf     DDSPort , FQud
        bcf     DDSPort , FData

        movlw   "T"             ;Echo back T[cr][lf] to show update done
        call    Send232
        movlw   0x0D
        call    Send232
        movlw   0x0A
        call    Send232
        goto    MainLoop
;--------------------------
DataTo9850
        movf    D0 , W          ;Load data into holding register for D0-3
        movwf   R0              ;needed so D0-3 don't get destroyed
        movf    D1 , W
        movwf   R1
        movf    D2 , W
        movwf   R2
        movf    D3 , W
        movwf   R3

        btfsc   PORTB , 7       ;B7=0 > Freq * 4   B7=1 > Freq
        goto    NoShift
        bcf     STATUS , C
        rlf     R0
        rlf     R1
        rlf     R2
        rlf     R3
        bcf     STATUS , C
        rlf     R0
        rlf     R1
        rlf     R2
        rlf     R3              ;Multiply freq word by 4
NoShift
        movf    R0 , W
        movwf   Temp
        call    Write9850
        movf    R1 , W
        movwf   Temp
        call    Write9850
        movf    R2 , W
        movwf   Temp
        call    Write9850
        movf    R3 , W
        movwf   Temp
        call    Write9850
        movf    D4 , W          ;Phase word
        movwf   Temp
        call    Write9850
        bcf     DDSPort , WClk
        return
;--------------------------
Write9850
        movlw   8
        movwf   BitCount
Loop9850
        bcf     DDSPort , WClk       ;data changes with -ve edge of clock
        rrf     Temp
        btfss   STATUS , C
        bcf     DDSPort , FData
        btfsc   STATUS , C
        bsf     DDSPort , FData
        call    Delay
        bsf     DDSPort , WClk     
        call    Delay 
        decfsz  BitCount         
        goto    Loop9850

        return
;-------------------------
SendInit
        movlw   0x0D
        call    Send232
        movlw   0x0A
        call    Send232         ;Startup message
        movlw   "9"
        call    Send232
        movlw   "8"
        call    Send232
        movlw   "5"
        call    Send232
        movlw   "0"
        call    Send232
        movlw   " "
        call    Send232
        movlw   "D"
        call    Send232
        movlw   "D"
        call    Send232
        movlw   "S"
        call    Send232
        movlw   " "
        call    Send232
        movlw   "C"
        call    Send232
        movlw   "o"
        call    Send232
        movlw   "n"
        call    Send232
        movlw   "t"
        call    Send232
        movlw   "r"
        call    Send232
        movlw   "o"
        call    Send232
        movlw   "l"
        call    Send232
        movlw   "l"
        call    Send232
        movlw   "e"
        call    Send232
        movlw   "r"
        call    Send232

        movlw   " "                 ;Instrument address
        call    Send232
        movlw   "A"
        call    Send232
        movlw   "d"
        call    Send232
        movlw   "d"
        call    Send232
        movlw   "r"
        call    Send232
        movlw   "."
        call    Send232
        movlw   " "
        call    Send232


        movf    InstAddr , W    ;Transfer register contents to SD so it
        movwf   SD              ;doesn't get corrupted
        swapf   SD , W          ;Send High nibble first
        andlw   0x0F
        call    HexToAscii
        call    Send232
        movf    SD , W          ;Now do the low nibble
        andlw   0x0F
        call    HexToAscii
        call    Send232

        movlw   0x0D
        call    Send232
        movlw   0x0A
        call    Send232
        return
;------------------------
Ack                    ;Send Z[cr][lf] back to PC to acknowledge etc.
        movlw   "Z"
        call    Send232
        movlw   0x0D
        call    Send232
        movlw   0x0A
        call    Send232
        return
;------------------------
        end