• Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Learning C

Mike (Pommie), has kindly offered to try and teach me to program microcontrollers in C, hence this thread.

Hopefully it may also help others on this journey.

Because I learn better by doing rather than reading and trying some inane example, we are going down the journey of converting one of my asm projects from my website - the Simple VFO project.

For the compiler, we are using MPlabX 5.45, XC8 and a 12F1840 PIC.

So here are both the schematic of the project and the current asm code (12F629 version - this code version is not on my website yet).



DDS VFO 8.JPG
Code:
;
; *******************************************************************************
; *                                        *
; *            VK5TM Simple VFO V2.3 - new encoder            *
; *                                        *
; *            2017_DDS_VFO_V2-3.asm                    *
; *                                        *
; *          Copyright 2021 Terry Mowles VK5TM                *
; *                                        *
; * This code may be used for personal, non-profit use only.            *
; *                                        *
; * Commercial use of this code or any derivatives of it is expressly forbidden.*
; * Use in any profit making enterprise of any kind is also expressly forbidden.*
; *                                        *
; * This code is in part based on the works of the following:            *
; *        Curtis W. Preuss - WB2V                        *
; *        Bob Okas - W3CD (SK)                        *
; *        Bruce Stough - AA0ED                        *
; *        Craig Johnson - AA0ZZ                        *
; *                                        *
; *******************************************************************************
;       
; *******************************************************************************
; *                                        *
; *    This code uses a 12F629 and a DDS module as a limited range VFO        *
; *                                        *
; *    Features:                                *
; *        Suitable for AD9850 or AD9851 DDS chips                *
; *        Upper and lower frequency limit                    *
; *        Frequency steps of 10Hz/1kHz/10kHz via encoder pushbutton    *
; *        Calibrate routine to account for DDS Xtal frequency variations    *
; *        2017 Update - Changed encoder & Switch inputs            *
; *                  Frequency save delay can be 1 to 255 seconds    *
; *                  Added code for LED indicator option        *
; *                  Calibration reset function added            *
; *                                        *
; *        2020 Update - Step size and LED step indication saved and    *
; *                  reinststated at power on                *
; *                                        *
; *        2021 March - Updated encoder routine                *
; *                                        *
; *    NOTE:    Once the calibrate routine has been done, entry into the    *
; *        calibrate routine is ignored on power up.            *
; *        It can be reset.                        *
; *                                        *
; *        See www.vk5tm.com for more information                *
; *                                        *
; *******************************************************************************
;
; *******************************************************************************
; *                                        *
; *            Target Controller -  PIC12F629                *
; *                ----\/----                    *
; *                 VCC |1        8| GND                    *
; *           CAL/STEP*---GP5 |2        7| GP0---DDS DATA            *
; *           ENCODER A---GP4 |3        6| GP1---DDS FU_UP            *
; *           ENCODER B---GP3 |4        5| GP2---DDS W_CLOCK            *
; *                        ----------                    *
; *    *GP5 is also LED step size output.                    *
; *    A 10k resistor must also be connected between pins 5 & 8 of the PIC    *
; *                                        *
; *******************************************************************************

; !!----- SELECT ONE OF THESE DDS TYPES AND TURN THE OTHER OFF -----!!

#define AD9850        ; Using the AD9850
;#define AD9851        ; Using the AD9851

; *******************************************************************************
; *    Device type and options.                        *
; *******************************************************************************
;
        processor 12F629
        radix     dec
    errorlevel -302  ; Skip out of bank nuisance messages

; *******************************************************************************
; *    Configuration fuse information for 12F629                *
; *******************************************************************************

        include   <P12F629.INC>

 __config _CP_OFF&_CPD_OFF&_BODEN_OFF&_MCLRE_OFF&_PWRTE_ON&_WDT_OFF&_FOSC_INTRCIO 
;                                                                             
; *******************************************************************************
; *     General equates.  These may be changed to accommodate the reference     *
; *    clock frequency, the desired upper frequency limit, the desired lower     *
; *    frequency limit, and the default startup frequency.            *
; *******************************************************************************
;
#ifdef AD9850
;    For 125 MHz Oscillator =======
ref_osc_3   equ 0x22        ; Most significant osc byte
ref_osc_2   equ 0x5C        ; Next byte
ref_osc_1   equ 0x17        ; Next byte
ref_osc_0   equ 0xD0        ; Least significant byte
#endif
#ifdef AD9851
;    For 180 MHz (30 MHz clock and 6x multiplier)                               
ref_osc_3   equ 0x17        ; Most significant osc byte               
ref_osc_2   equ 0xDC        ; Next byte                               
ref_osc_1   equ 0x65        ; Next byte                               
ref_osc_0   equ 0xDE        ; Least significant byte                   
#endif
;
;    Change limits to suit
;
;    Limit_0...3 contains the upper limit frequency as a 32 bit integer.
;
limit_3   equ 0x00        ; Most significant byte for 5.5 MHz   
limit_2   equ 0x53        ; Next byte
limit_1   equ 0xEC        ; Next byte
limit_0   equ 0x60        ; Least significant byte
;
;    Low_limit_3..0 contains the lower frequency limit as a 32 bit integer
;
limit_low_3 equ 0x00        ; Most significant byte for 5.0 MHz
limit_low_2 equ 0x4C        ; Next byte
limit_low_1 equ 0x4B        ; Next byte
limit_low_0 equ 0x40        ; Least significant byte
;
;    Default contains the default startup frequency as a 32 bit integer.
;    This will be overwritten by the frequency save routine in normal use.
;
default_3 equ 0x00        ; Most significant byte for 5.0 MHz
default_2 equ 0x4C        ; Next byte
default_1 equ 0x4B        ; Next byte
default_0 equ 0x40        ; Least significant byte
;
;    Frequency at which Calibration will take place
;
cal_freq_3 equ 0x00        ; Most significant byte for 5.250 MHz
cal_freq_2 equ 0x50        ; Next byte
cal_freq_1 equ 0x1B        ; Next byte
cal_freq_0 equ 0xD0        ; Least significant byte
;
; *******************************************************************************
; *    Setup the initial constant, based on the frequency of the reference    *
; *    oscillator.  This can be tweaked with the calibrate function.        *
; *    Stored in EEPROM                            *
; *******************************************************************************
;
        ORG     0x2100
;    ref_osc bytes must be first 4 bytes of EEPROM                               
        DATA    ref_osc_0
        DATA    ref_osc_1
        DATA    ref_osc_2
        DATA    ref_osc_3

;    startup frequency bytes must be next 4 bytes of EEPROM                     
        DATA    default_0    ; startup -> freq_0                               
        DATA    default_1    ; startup -> freq_1                               
        DATA    default_2    ; startup -> freq_2                               
        DATA    default_3    ; startup -> freq_3
    DATA    0        ; Step size
    DATA    0        ; Cal flag
;
; *******************************************************************************
; *    Assign names to IO pins.                        *
; *******************************************************************************
;
#DEFINE DDS_clk        GPIO,2    ; AD9850/AD9851 write clock
#DEFINE DDS_dat        GPIO,0    ; AD9850/AD9851 serial data input
#DEFINE DDS_load    GPIO,1    ; Update pin on AD9850/AD9851

; ------------Interupt defines---------------------------------------------------
;
w_temp        EQU     0x5E    ; variable used for context saving
status_temp   EQU     0x5F    ; variable used for context saving   

delay    equ    2        ; number of seconds before save frequency,
                ; valid values 1=>255
                ; NOTE: Not exact number of seconds as Timer0 count
                ; is 0.999936 seconds
;
; *******************************************************************************
; *    Allocate variables in general purpose register space            *
; *******************************************************************************
;
    CBLOCK    0x20        ; Start Data Block

    AD9851_0        ; AD9850/AD9851 control word               
    AD9851_1        ;  (5 bytes)                               
    AD9851_2                                                           
    AD9851_3                                                           
    AD9851_4                                                           
    fstep_0            ; Frequency inc/dec
    fstep_1            ;  (4 bytes)
    fstep_2
    fstep_3
    low_limit_3        ; Low frequency limit
    low_limit_2        ; (4 bytes)
    low_limit_1
    low_limit_0
    mult_count        ; Used in calc_dds_word
    bit_count        ;   "
    byte2send        ;
    osc_temp_0        ; Oscillator frequency
    osc_temp_1        ;  (4 bytes)
    osc_temp_2       
    osc_temp_3
    timer1            ; Used in delay routines
    timer2            ;   "

    dir            ; for encoder routine
    previous
    clicks
    temp            ; for encoder routine

    saved            ; Flags for frequency save routine
                ; saved,0 used in read encoder routine to jump flag for frequency save
                ; =1 calibrate mode active
                ; =0 calibrate mode not active
                ; saved,2 used in interupt routine

    CNT1            ; Counter for interrupt routine
    CNT2            ;    "     "      "        "                         
    freq_0            ; Frequency (hex)
    freq_1            ; (4 bytes)           
    freq_2            ; 
    freq_3            ;   
    osc_0            ; Current oscillator
    osc_1            ; (4 bytes)
    osc_2
    osc_3
    cal_flag        ; Flag to test if CAL done. Bit 0 = 1 if done
    step_size        ; 0=10Hz 1=1kHz 2=10kHz step size
    count
    ENDC            ; End of Data Block

; *******************************************************************************
; *    The 12F629 resets to 0x00.                        *
; *    The Interrupt vector is at 0x04.                    *
; *******************************************************************************
;
    ORG     0x0000               
    goto    start        ; Jump to main program

    ORG     0x0004        ; interrupt routine for approx 2 second counter
    movwf   w_temp        ; save off current W register contents
    movf    STATUS,w    ; move status register into W register
    movwf    status_temp    ; save off contents of STATUS register
    bcf    INTCON,T0IF    ; Clear Timer0 interrupt flag
    decfsz     CNT1,F         ; Decrease CNT1. If zero, skip next instruction
    goto    not_yet     ; Not zero goto not yet
    decfsz    CNT2,f
    goto    not_yet2     ; Not zero goto not yet2
    bsf    saved,2        ; set interrupt timed out flag
    movlw    delay        ; Delay x number of seconds
    movwf    CNT2

not_yet2
    movlw    18
    movwf    CNT1
not_yet
    movlw    39
    movwf    TMR0         ; Reload Timer0
;
exit_interrupt
;
    movf    status_temp,w    ; retrieve copy of STATUS register
    movwf    STATUS        ; restore pre-isr STATUS register contents
    swapf   w_temp,f
    swapf   w_temp,w    ; restore pre-isr W register contents
    retfie             ; Enable general interrupts and return
;
enc_table
    addwf    PCL,f        ;index into table
    dt    0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0
;
; *******************************************************************************
; *                                        *
; * Purpose:  This is the start of the program.  It detects whether to enter    *
; *           calibrate mode.  If so, it calls the Calibrate routine.          *
; *           Otherwise, it sets the power-on frequency    and enters the loop to     *
; *           poll the encoder.                            *
; *                                        *
; *   Input:  The start up frequency is defined in the default_3...0        *
; *           definitions above and relies on the reference oscillator        *
; *           constant defined in ref_osc_3...ref_osc_0.            *
; *                                        *
; *  Output:  Normal VFO operation.                        *
; *                                        *
; *******************************************************************************
;
start
    clrf    GPIO
    movlw    0x07        ; Code to turn off the analog comparitors
    movwf    CMCON        ;
    bsf    STATUS,RP0    ; Switch to bank 1
    call    0x3FF        ; retrieve factory calibration value
    movwf    OSCCAL        ; update register with factory cal value
    movlw    b'10100111'    ; GPIO pull-ups disabled, TMR0 clock source internal
                ; clock, prescaler to TMR0, set TMR0 prescaler 1:256
    movwf    OPTION_REG    ;
;--------------------------------------------------------------------------------
    movlw    b'00111100'    ; GPIO 2,3,4,5 inputs 0,1 outputs
    movwf    TRISIO        ;
    BANKSEL GPIO         ;
;
; *******************************************************************************
; *                                        *
; *    Clear cal_flag in EEPROM - enables calibration to be re-done without    *
; *    re-programming PIC                            *
; *                                        *
; *******************************************************************************
;
    btfss    GPIO,2        ; Test if GPIO,2 is high (1)
    goto    continue    ; Not high, continue

    BANKSEL EEADR        ; Switch to EEPROM bank
    movlw    9        ;
    movwf    EEADR        ; Point to flag location in EEprom
    clrf    EEDATA        ; Clear EEDATL so '0' is written to EEPROM
    call    write_EEPROM    ; Write it
    BANKSEL GPIO        ; Back to bank 0
;    nop            ; Stay here until powered down and everything
;    goto    $-1        ; restored to normal.
    sleep

continue
    BANKSEL TRISIO        ; Reset ports
    movlw    b'00111000'    ; PORTA 3,4,5 inputs 0,1,2 outputs
    movwf    TRISIO        ;
    BANKSEL GPIO         ;
;--------------------------------------------------------------------------------
    clrf    INTCON        ; clear INTCON
    clrf    step_size
    clrf    cal_flag    ;
;
;    Initialize DDS Module with zero freq
;
    clrf    AD9851_0    ; AD9850/51 control word
    clrf    AD9851_1    ; (5 bytes)
    clrf    AD9851_2
    clrf    AD9851_3
    clrf    AD9851_4
    call    send_dds_word    ; Send it to the DDS
    call    send_dds_word    ; twice to be sure
;
    movlw    limit_low_3    ; Most significant byte for lower limit
    movwf    low_limit_3
    movlw    limit_low_2    ; Next byte
    movwf    low_limit_2
    movlw    limit_low_1    ; Next byte
    movwf    low_limit_1
    movlw    limit_low_0    ; Least significant byte
    movwf    low_limit_0
;
;       Enter Calibrate Mode if GPIO,5 is low when turning the power on and CAL not already done.
;    Test flag to see if CAL already done. Jump over this section if done.
    movlw    9        ;
    BANKSEL    EEADR        ; Switch to bank 1
    movwf    EEADR        ; Point to flag location in EEprom
    call    read_EEPROM    ; Read EEprom at address 9
    BANKSEL    GPIO        ; Switch to bank 0
    movwf    cal_flag    ; Move data to register
    btfsc    cal_flag,0    ; Test set_flag,0 - will be 0 if CAL not done
    goto    read_EEocs    ; or 1 if CAL already done
    btfss    GPIO,5        ; Cal pushbutton pressed
    call    calibrate    ; Yes,call calibration routine
;
;       Get the reference oscillator constant from the EEPROM.
;
read_EEocs
;
    bsf    STATUS,RP0    ; Switch to bank 1
    clrf    EEADR        ; Reset the EEPROM read address to 0
    call    read_EEPROM    ; Read EEPROM (all in bank 1)
    movf    EEDATA,w    ; Get the first osc byte
    movwf    osc_0        ; Save osc frequency
    call    read_EEPROM    ; Read EEPROM
    movf    EEDATA,w    ;
    movwf    osc_1        ; Save it
    call    read_EEPROM    ; Read EEPROM
    movf    EEDATA,w    ;
    movwf    osc_2        ; Save it
    call    read_EEPROM    ; Read EEPROM
    movf    EEDATA,w    ;
    movwf    osc_3        ; Save it
;
;       Set the power on frequency to the defined value.
;       (They always follow the osc bytes)                                     
;
    call    read_EEPROM    ; Read EEPROM                               
    movf    EEDATA,w    ; Get the first default freq byte           
    movwf    freq_0        ; Save it
    call    read_EEPROM    ; Read EEPROM                               
    movf    EEDATA,w    ; Get the next freq byte                     
    movwf    freq_1        ; Save it
    call    read_EEPROM    ; Read EEPROM                               
    movf    EEDATA,w    ; Get the next freq byte                     
    movwf    freq_2        ; Save it                                   
    call    read_EEPROM    ; Read EEPROM                                 
    movf    EEDATA,w    ; Get the last freq byte                     
    movwf    freq_3        ; Save it

    call    read_EEPROM    ; Read EEPROM                                 
    movf    EEDATA,w    ; Get step size                     
    movwf    step_size    ; Save it

    bcf    STATUS,RP0    ; Back to bank 0

    call    step_led    ; Set step size LED's
;
;    Send power on frequency to the DDS chip.
;
    call    calc_dds_word    ; Convert to delta value
    call    send_dds_word    ; Send the power-on frequency to the
                ; AD9850/AD9851 in serial mode
;
;    Get the power on encoder value.
    movf    GPIO,w
    movwf   previous    ; Save it in ren_old
    movlw    b'00011000'    ; Get encoder mask (GPIO,4 and GPIO,3)
    andwf   previous,f    ; Get encoder bits and zero all other bits
    clrf    dir        ; Clear the knob direction indicator
;
    clrf    saved        ;
;
;    Setup interupt on change pins
    bsf    STATUS,RP0    ; Switch to bank 1
    movlw    b'00111000'    ; Set GPIO,3,4 & 5 to interupt on change
    movwf    IOC        ;
    bcf    STATUS,RP0    ; Back to bank 0
;
; Fall into the Main Program Loop
;
; *******************************************************************************
; *                                        *
; * Purpose:    This is the Main Program Loop. The program's main loop        *
; *        calls poll_encoder, which continuously polls the rotary shaft    *
; *        encoder.  When the shaft encoder has changed, the direction    *
; *        it moved is determined and stored in last_dir.  The subroutine    *
; *        then returns to main.                        *                       
; *                                        *
; *        The variable fstep is added or subtracted from the current    *
; *        VFO frequency and stored in freq.                *
; *        Next, the subroutine calc_dds_word is used to calculate the DDS    *
; *        frequency control word from the values in freq and osc.        *
; *        The result is stored in AD9850/AD9851. This data is transferred    *
; *        to the AD9851 DDS chip by calling the subroutine send_dds_word.    *
; *                                        *
; *        The frequency is saved to EPROM X seconds (as set by 'delay')    *
; *        after the encoder stops turning and the PIC goes to sleep until    *
; *        the encoder is turned again                    *
; *                                        *
; *   Input:    None.                                *
; *                                        *
; *  Output:    None.                                *
; *                                        *
; *******************************************************************************
;
    bsf    INTCON,T0IE    ; Enable Timer0 interrupt
    bsf    INTCON,GIE    ; Enable general interrupts

main

    call    poll_encoder    ; Check for knob movement (wait there!)     
                ; Return here when encoder change detected
    bsf    STATUS,RP0    ; Switch BANK1
    bcf    OPTION_REG,T0CS    ; Start Timer0
    bcf    STATUS,RP0    ; Switch to BANK0
    movlw    18        ; Interrupt counter for ~1 second
    movwf    CNT1
    movlw    delay        ; Delay x number of seconds
    movwf    CNT2
    movlw    39
    movwf    TMR0        ; Writing this will restart Timer0 after two ins. cyc.
;
; *******************************************************************************
;    Code for pushbutton selection of step size                *
;    Step size 10Hz/1kHz/10kHz                        *
; *******************************************************************************
step                ;
    clrf    fstep_3        ;
    clrf    fstep_2        ;
    clrf    fstep_1        ;

step1
    movfw    step_size    ; If step_size = 0
    xorlw    0
    btfsc    STATUS,Z
    goto    step_10hz    ; Set step to 10Hz

    movfw   step_size    ; If step_size = 1
    xorlw   1
    btfsc   STATUS,Z
    goto    step_1khz    ; Set step to 1kHz

                ; Else step_size = 10kHz

    movlw    0x10        ; 10kHz steps
    movwf    fstep_0        ;
    movlw    0x27        ;
    movwf    fstep_1        ;
    goto    stepdir

step_10hz            ;
    movlw    0x0A        ; 10Hz steps
    movwf    fstep_0        ;
    goto    stepdir

step_1khz
    movlw    0xE8        ; 1kHz steps
    movwf    fstep_0        ;
    movlw    0x03        ;
    movwf    fstep_1        ;
;
;    Based on the knob direction, either add or subtract the increment,
;    then update DDS.
;
stepdir
    btfsc    dir,0        ; Is the knob going up?
    goto    up        ; Yes, then add the increment
down
    call    sub_step    ; Subtract fstep from freq
    goto    update        ; Update DDS                         
up
    call    add_step    ; Add fstep to freq
    call    check_add    ; Make sure we did not exceed the maximum
update
    call    calc_dds_word    ; Calculate the control word for the DDS chip
    call    send_dds_word    ; Send the control word to the DDS chip
;
    goto    main        ; Continue main loop
;
; *******************************************************************************
; *                                        *
; * Purpose:    This routine adds the 32 bit value of fstep to the 32 bit    *
; *        value in freq.  When incrementing, the fstep value is a        *
; *        positive integer.  When decrementing, fstep is the complement    *
; *        of the value being subtracted.                    *
; *                                        *
; *   Input:    The 32 bit values in fstep and freq                *
; *                                        *
; *  Output:    The sum of fstep and freq is stored in freq.  When incrementing    *
; *        this value may exceed the maximum.  When decrementing, it may   *
; *        go negative.                                                    *
; *                                         *
; *******************************************************************************
;
add_step
    movf    fstep_0,w    ; Get low byte of the increment
    addwf    freq_0,f    ; Add it to the low byte of freq
    btfss    STATUS,C    ; Any carry?
    goto    add1        ; No, add next byte
    incfsz    freq_1,f    ; Ripple carry up to the next byte
    goto    add1        ; No new carry, add next byte
    incfsz    freq_2,f    ; Ripple carry up to the next byte
    goto    add1        ; No new carry, add next byte
    incf    freq_3,f    ; Ripple carry up to the highest byte
add1
    movf    fstep_1,w    ; Get the next increment byte
    addwf    freq_1,f    ; Add it to the next higher byte
    btfss    STATUS,C    ; Any carry?
    goto    add2        ; No, add next byte
    incfsz    freq_2,f    ; Ripple carry up to the next byte
    goto    add2        ; No new carry, add next byte
    incf    freq_3,f    ; Ripple carry up to the highest byte
add2
    movf    fstep_2,w    ; Get the next to most significant increment
    addwf    freq_2,f    ; Add it to the freq byte
    btfss    STATUS,C    ; Any carry?
    goto    add3        ; No, add last byte
    incf    freq_3,f    ; Ripple carry up to the highest byte
add3
    movf    fstep_3,w    ; Get the most significant increment byte
    addwf    freq_3,f    ; Add it to the most significant freq
    return            ; Return to the caller
;
; *******************************************************************************
; *                                        *
; * Purpose:    Check if freq exceeds the upper limit.                *
; *                                        *
; *   Input:    The 32 bit values in freq                    *
; *                                        *
; *  Output:    If freq is below the limit, it is unchanged.  Otherwise, it is    *
; *        set to equal the upper limit.                    *
; *                                        *
; *******************************************************************************
;
check_add
;
;    Check the most significant byte.
;
    movlw    0xFF-limit_3    ; Get (FF - limit of high byte)
    addwf    freq_3,w    ; Add it to the current high byte
    btfsc    STATUS,C    ; Was high byte too large?
    goto    set_max        ; Yes, apply limit
    movlw    limit_3        ; Get high limit value
    subwf    freq_3,w    ; Subtract the limit value
    btfss    STATUS,C    ; Are we at the limit for the byte?
    goto    exit1        ; No, below.  Checks are done.
;
;    Check the second most significant byte.
;
    movlw    0xFF-limit_2    ; Get (FF - limit of next byte)
    addwf    freq_2,w    ; Add it to the current byte
    btfsc    STATUS,C    ; Is the current value too high?
    goto    set_max        ; Yes, apply the limit
    movlw    limit_2        ; Second limit byte
    subwf    freq_2,w    ; Subtract limit value
    btfss    STATUS,C    ; Are we at the limit for the byte?
    goto    exit1        ; No, below.  Checks are done.
;
;    Check the third most significant byte.
;
    movlw    0xFF-limit_1    ; Get (FF - limit of next byte)
    addwf    freq_1,w    ; Add it to the current byte
    btfsc    STATUS,C    ; Is the current value too high?
    goto    set_max        ; Yes, apply the limit
    movlw    limit_1        ; Third limit byte
    subwf    freq_1,w    ; Subtract limit value
    btfss    STATUS,C    ; Are we at the limit for the byte?
    goto    exit1        ; No, below.  Checks are done.
;
;    Check the least significant byte.
;
    movlw    limit_0        ; Fourth limit byte
    subwf    freq_0,w    ; Subtract limit value
    btfss    STATUS,C    ; Are we at the limit for the byte?
    goto    exit1        ; No, below.  Checks are done.
set_max
    movlw    limit_0        ; Get least significant limit
    movwf    freq_0        ; Set it in freq
    movlw    limit_1        ; Get the next byte limit
    movwf    freq_1        ; Set it in freq_1
    movlw    limit_2        ; Get the next byte limit
    movwf    freq_2        ; Set it in freq_2
    movlw    limit_3        ; Get the most significant limit
    movwf    freq_3        ; Set it in freq_3
exit1
    return            ; Return to the caller
;
; *******************************************************************************
; *                                        *
; * Function:    sub_step                            *
; *                                        *
; * Purpose:    Subtract the increment step from freq.                *
; *                                        *
; *   Input:    The values in fstep and freq_3..0.                *
; *                                        *
; *  Output:    None                                *
; *                                        *
; * Revisions:    Modified for limited range VFO. 29-9-13 VK5TM            *
; *                                        *
; *******************************************************************************
;
sub_step
;
    call    invert_fstep    ; Invert fstep_3..0 to perform the subtraction
    call    add_step    ; Add the complement to do the subtraction
;
; *******************************************************************************
; *                                        *
; * Function:    low_limit_chk                            *
; *                                        *
; * Purpose:    Test the new frequency to see if it is above the lower band    *
; *        limit. If not, the frequency is set to the band lower limit.    *
; *                                        *
; * Input:    Freq_0...3 and Low_limit_0...3                    *
; *                                        *
; * Output:    Original frequency if above low limit or low_limit_0..3 in    *
; *        Freq_0...3                            *
; *                                        *
; *******************************************************************************
;
low_limit_chk
;       Check the most significant byte.
;
    btfsc    freq_3,7
    goto    set_low           ; Yes, set to lower frequency limit
    movf    freq_3,w
    subwf    low_limit_3,w
    btfss     STATUS,C          ; Are we at the limit for the byte?
    goto    limit_exit        ; No, above.
    btfss    STATUS,Z          ; Are the bytes equal?
    goto    set_low           ; No, so vfo_X_3 > limit_3.
;
;       Check the second most significant byte when MSB equals limit_3
;
    movf    freq_2,w
    subwf    low_limit_2,w
    btfss    STATUS,C          ; Are we at the limit for the byte?
    goto    limit_exit        ; No, above.  Check next.
    btfss    STATUS,Z          ; Might they be equal?
    goto    set_low           ; Nope, so vfo_X_2 > limit_2
;
;       Check the third most significant byte.
;
    movf    freq_1,w
    subwf    low_limit_1,w
    btfss    STATUS,C          ; Are we at the limit for the byte?
    goto    limit_exit        ; No, above.  Checks are done.
    btfss    STATUS,Z          ; Check if the bytes are equal
    goto    set_low           ; No, so vfo_X_1 > limit_1
;
;       Check the least significant byte.
;
    movf    freq_0,w
    subwf    low_limit_0,w
    btfss    STATUS,C          ; Are we at the limit for the byte?
    goto    limit_exit        ; No, above.  Checks are done.     
;
;    The frequency is below the band lower limit. Set frequency to the
;    band starting frequency.       
set_low
;
    movf    low_limit_0,w
    movwf    freq_0
    movf    low_limit_1,w
    movwf    freq_1
    movf    low_limit_2,w
    movwf    freq_2
    movf    low_limit_3,w
    movwf    freq_3
;
limit_exit
    call    invert_fstep    ; Put fstep back to original value
    return            ; Return to caller
;
; *******************************************************************************
; *                                        *
; * Function:    invert_fstep                            *
; *                                        *
; *  Purpose:    Support function for sub_step and calibrate. This function    *
; *        negates the value of fstep_3..0 to assist in the frequency    *
; *        decrement. This operation is performed twice by sub_step, and    *
; *        is also used by calibrate.                    *
; *                                        *
; *    Input:    fstep_3, fstep_2, fstep_1, fstep_0                *
; *                                        *
; *   Output:    fstep_3..0 contain the 2's complement of their original value    *
; *                                        *
; *******************************************************************************
;
invert_fstep
                ; Invert the bits in
    comf    fstep_0,f    ; fstep_0
    comf    fstep_1,f    ; fstep_1
    comf    fstep_2,f    ; fstep_2
    comf    fstep_3,f    ; fstep_3
    incfsz    fstep_0,f    ; Now incremnt fstep_0 to get 2's complement
    goto    invert_done    ; If fstep_0 > 0, then done
                ; Else, there was a carry out of fstep_0
    incfsz    fstep_1,f    ; Add 1 to fstep_1
    goto    invert_done    ; If fstep_1 > 0, then done
                ; Else, there was a carry out of fstep_1
    incfsz    fstep_2,f    ; Add 1 to fstep_2
    goto    invert_done    ; if fstep_2 > 0, then done
                ; Else, there was a carry out of fstep_2
    incf    fstep_3,f    ; Increment the high byte
;
invert_done
    return            ; Back to caller
;
; *******************************************************************************
; *                                        *
; * Purpose:    This routine does the following:                *
; *                                        *
; *        Reads the encoder bits until a change is detected, then        *
; *        determines the direction the knob was moved.            *
; *                                        *
; *   Input:    Knob input read from GPIO                    *
; *        ren_old -> the last encoder bits read                           *
; *        last_dir -> the last direction moved                            *
; *                                        *
; *  Output:    ren_new -> the current encoder bits                *
; *        last_dir -> the last direction (0 = down, 1 = up)        *
; *                                        *
; *******************************************************************************
;
poll_encoder
;
    btfsc    saved,0        ; Test if in calibrate mode - ignore interrupt flags
    goto    read_encoder
    btfsc    saved,2        ; Test interrupt flag, jump over if not set
    call    update_EEPROM    ; Call to save freq when changed and timer timed out
;
; *******************************************************************************
; *    Code for pushbutton selection of step size                *
; *******************************************************************************
    btfsc    GPIO,5        ; is GPIO,5 low
    goto    read_encoder    ; no

    incf    step_size,f

    movfw   step_size    ; Keep step_size in range of 0 - 2
    xorlw   3
    btfsc   STATUS,Z
    clrf    step_size

step_exit
    btfss    GPIO,5
    goto    $-1
    call    step_led

read_encoder
    rlf    previous,f
    rlf    previous,w
    andlw    0x60        ;keep only bits 5 & 6
    btfss    GPIO,3        ;move encoder bits
    iorlw    8        ;to bits 3 & 4
    btfss    GPIO,4
    iorlw    16
    movwf    previous    ;keep for next time

    rlf    previous,w
    andlw    0xF0
    movwf    temp
    swapf    temp,w
    call    enc_table

    addwf    clicks,f
    movlw    3
    xorwf    clicks,w
    btfsc    STATUS,Z
    goto    exit_up
    movlw    0xfc        ;-4
    xorwf    clicks,w
    btfsc    STATUS,Z
    goto    exit_down
    goto    poll_encoder

exit_down
    clrf    clicks
    bcf    dir,0        ; Set "DOWN"
    return
exit_up
    clrf    clicks
    bsf    dir,0        ; Set "UP"
    return
;
; *******************************************************************************
; *                                        *
; *    Set step size and output pulses to indicate step size            *
; *                                        *
; *******************************************************************************
step_led                ;
    movfw    step_size    ; If step_size = 0
    xorlw    0
    btfsc    STATUS,Z
    goto    step_led10hz    ; Set 10Hz LED

    movfw   step_size    ; If step_size = 1
    xorlw   1
    btfsc   STATUS,Z
    goto    step_led1khz    ; Set 1kHz LED
;
;                ; Else set 10kHz LED
;
    call    button_out    ; 10kHz LED
    call    wait_100us
    bsf    GPIO,5        ; send 3 pulses for 10kHz LED
    call    wait_1ms
    bcf    GPIO,5
    call    wait_100us    ;
    bsf    GPIO,5
    call    wait_1ms
    bcf    GPIO,5
    call    wait_100us    ;
    bsf    GPIO,5
    call    button_in
    return

;
step_led10hz            ;
    call    button_out    ; 10Hz LED
    call    wait_100us
    bsf    GPIO,5        ; send 1 pulse for 10Hz LED
    call    button_in
    return
;
step_led1khz
    call    button_out    ; 1kHz LED
    call    wait_100us
    bsf    GPIO,5        ; send 2 pulses for 1kHz LED
    call    wait_1ms
    bcf    GPIO,5
    call    wait_100us    ;
    bsf    GPIO,5
    call    button_in
    return
;               
button_out    ; Set GPIO,5 as output
    call    wait_10ms    ; reset 4017
    bsf    STATUS,RP0    ; Switch to bank 1
    movlw    b'00011000'    ; GPIO 3,4 inputs 0,1,2,5 outputs     
    movwf    TRISIO        ;
    bcf    STATUS,RP0    ; Switch back to bank 0
;--------------------------------------------------------------------------------
    bsf    GPIO,5
    call    wait_10ms    ; reset 4017
    bcf    GPIO,5
;--------------------------------------------------------------------------------
    return
;
button_in    ; Set GPIO,5 as input
    bsf    STATUS,RP0    ; Switch to bank 1
    movlw    b'00111000'    ; GPIO 3,4,5 inputs 0,1,2 outputs     
    movwf    TRISIO        ;
    bcf    STATUS,RP0    ; Switch back to bank 0

    return
;     
; *******************************************************************************
; *                                        *
; * Purpose:    This routine is entered at start up if the calibrate pads are    *
; *        shorted at power-on time.                    *
; *                                        *
; *         The DDS chip is programmed to produce a frequency, based on the    *
; *        osc value stored in the EEPROM and the calibration frequency as    *
; *        at the head of the code. As long as the pads are shorted, the    *
; *        osc value is slowly altered to allow the output    to be trimmed.     *
; *        Once the encoder is turned after the short is removed from the     *
; *        Cal pads, the new osc value is stored in the EEPROM and normal    *
; *        operation begins.                        *
; *                                        *
; *   Input:    The original osc constant in EEPROM                *
; *                                          *
; *  Output:    The corrected osc constant in EEPROM                *
; *                                        *
; *******************************************************************************
;
calibrate
    bsf    saved,0        ; set flag for poll encoder routine
    bcf    INTCON,GIE    ; Turn off interupts to be sure
;
    movlw    cal_freq_0    ; Move Calibration frequency constants
    movwf    freq_0        ; to freq_0...3 for Calibration routine.
    movlw    cal_freq_1    ;
    movwf    freq_1        ;   
     movlw    cal_freq_2    ;
    movwf    freq_2        ;   
    movlw    cal_freq_3    ;
    movwf    freq_3        ;   
;
;    Read the starting reference oscillator value from EEPROM.
;
    bsf    STATUS,RP0    ; Switch to bank 1
    clrf    EEADR        ; Reset the EEPROM read address
    call    read_EEPROM    ; Read first byte from EEPROM (all in bank 1)
    movf    EEDATA,w    ; Get the first osc byte
    movwf    osc_0        ; Save osc frequency
    call    read_EEPROM    ; Read second byte from EEPROM
    movf    EEDATA,w    ;
    movwf    osc_1        ; Save it
    call    read_EEPROM    ; Read third byte from EEPROM
    movf    EEDATA,w    ;
    movwf    osc_2        ; Save it
    call    read_EEPROM    ; Read fourth byte from EEPROM
    movf    EEDATA,w    ;
    movwf    osc_3        ; Save it
    bcf    STATUS,RP0    ; Back to bank 0 for store
;
cal_loop
    call    calc_dds_word    ; Calculate DDS value based on current osc
    call    send_dds_word    ; Update the DDS chip
    call    poll_encoder    ; Wait until the encoder has moved.
    btfsc    GPIO,5        ; Calibrate switch/jumper still set?
    goto    cal_out        ; No, go to exit and save values to EEPROM
    clrf    fstep_3        ; Clear the three most significant
    clrf    fstep_2        ; bytes of fstep
    clrf    fstep_1        ;
    movlw    0x10        ;
    movwf    fstep_0        ; Use small increment
    nop            ; Wait a cycle
    btfsc    dir,0        ; Are we moving down?
    goto    faster        ; No, increase the osc value
;
;    slower
;
    comf    fstep_0,f    ; Subtraction of fstep is done by
    comf    fstep_1,f    ; adding the twos compliment of fsetp
    comf    fstep_2,f    ; to osc
    comf    fstep_3,f    ;
    incfsz    fstep_0,f    ; Increment last byte
    goto    faster        ; Non-zero, continue
    incfsz    fstep_1,f    ; Increment next byte
    goto    faster        ; Non-zero, continue
    incfsz    fstep_2,f    ; Increment next byte
    goto    faster        ; Non-zero, continue
    incf    fstep_3,f    ; Increment the high byte
faster
    movf    fstep_0,w    ; Get the low byte increment
    addwf    osc_0,f        ; Add it to the low osc byte
    btfss    STATUS,C    ; Was there a carry?
    goto    add4        ; No, add the next bytes
    incfsz    osc_1,f        ; Ripple carry up to the next byte
    goto    add4        ; No new carry, add the next bytes
    incfsz    osc_2,f        ; Ripple carry up to the next byte
    goto    add4        ; No new carry, add the next bytes
    incf    osc_3,f        ; Ripple carry up to the highest byte
add4
    movf    fstep_1,w    ; Get the second byte increment
    addwf    osc_1,f        ; Add it to the second osc byte
    btfss    STATUS,C    ; Was there a carry?
    goto    add5        ; No, add the third bytes
    incfsz    osc_2,f        ; Ripple carry up to the next byte
    goto    add5        ; No new carry, add the third bytes
    incf    osc_3,f        ; Ripple carry up to the highest byte
add5
    movf    fstep_2,w    ; Get the third byte increment
    addwf    osc_2,f        ; Add it to the third osc byte
    btfss    STATUS,C    ; Was there a carry?
    goto    add6        ; No, add the fourth bytes
    incf    osc_3,f        ; Ripple carry up to the highest byte
add6
    movf    fstep_3,w    ; Get the fourth byte increment
    addwf    osc_3,f        ; Add it to the fourth byte
    goto    cal_loop    ; Yes, stay in calibrate mode
;
;    Write final value to EPROM
;
cal_out
    bsf    STATUS,RP0    ; Switch to bank 1
    movf    osc_0,w        ; Get the first osc byte to record
    clrf    EEADR        ; osc bytes start at EEPROM address 0
    movwf    EEDATA        ; Put byte in EEPROM write location
    call    write_EEPROM    ;
    movf    osc_1,w        ; Get the second byte to record
    movwf    EEDATA        ; Put byte in EEPROM write location
    call    write_EEPROM    ;
    movf    osc_2,w        ; Get the third byte to record
    movwf    EEDATA        ; Put byte in EEPROM write location
    call    write_EEPROM    ;
    movf    osc_3,w        ; Get the fourth byte to record
    movwf    EEDATA        ; Put byte in EEPROM write location
    call    write_EEPROM    ;
    movlw    9
    movwf    EEADR        ; Move to EEPROM write location
;    bsf    cal_flag,0    ; Set bit 0 of cal_flag => CAL done
;    movfw    cal_flag    ; Move file to w for save to EEprom
    movlw    1       
    movwf    EEDATA        ; Put byte in EEprom write location
    call    write_EEPROM    ;

    bcf    STATUS,RP0    ; Back to bank 0
    bcf    saved,0        ; clear flag used poll encoder routine
    return            ; Return to the caller
;                     
; *******************************************************************************
; *                                        *
; * Purpose:    This routine will save the current frequency in EEPROM. This    *
; *        frequency will then be used as the initial frequency upon start    *
; *        up. Frequency is automatically saved 2 seconds after encoder    *
; *        stops moving                            *
; *                                        *
; *   Input:    The constants in freq_0...3                    *
; *                                        *
; *  Output:    None                                *
; *                                        *
; *******************************************************************************
;
update_EEPROM
    bcf    INTCON,GIE    ; turn interrupts off
    BTFSC     INTCON,GIE     ; See AN576 - make sure interrupts are off
    GOTO     $-2
    bcf    saved,2
    bsf    STATUS,RP0    ; Switch BANK1
    bsf    OPTION_REG,T0CS    ; Turn off Timer0
    movlw    4        ; Default startup frequency address location
    movwf    EEADR        ; and set up for start of EEPROM writes   
    movf    freq_0,w    ; Get the first freq byte to write
    movwf    EEDATA        ; First freq byte to EEPROM Write register
    call    write_EEPROM    ; Write it
    movf    freq_1,w    ; Get the second freq byte to write
    movwf    EEDATA        ; Second freq byte to EEPROM Write register
    call    write_EEPROM    ; Write it
    movf    freq_2,w    ; Get the third freq byte to write
    movwf    EEDATA        ; Third freq byte to EEPROM Write register
    call    write_EEPROM    ; Write it
    movf    freq_3,w    ; Get the fourth freq byte to write
    movwf    EEDATA        ; Fourth freq byte to EEPROM Write register
    call    write_EEPROM    ; Write it

    movf    step_size,w    ; Get the step size to write
    movwf    EEDATA        ;
    call    write_EEPROM    ; Write it

    bcf    STATUS,RP0    ; Back to bank 0
    call    nod_off        ; Go to sleep until encoder moved
    bsf    INTCON,GIE    ; turn interrupts on
    return            ;   
;
; *******************************************************************************
; *                                        *
; *    Sleep routine.                                 *
; *    Puts PIC to sleep and waits for encoder to move.             *
; *                                        *
; *                                        *
; *******************************************************************************
;
nod_off
;    Setup interupt on change pins
    bsf    INTCON,GPIE    ; Enable Port Change Interupt
    movf    GPIO,w        ; clear the change condition (see 12F629 data sheet)
    bcf    INTCON,GPIF    ; clear the interrupt flag
    sleep            ; Night,night. Sweet dreams.
    nop            ; Wake from sleep (may still be a bit drowsy)
    bcf    INTCON,GPIE    ; Disable Port Change Interupt
    return            ; Back to work
;
; *******************************************************************************
; *                                        *
; *        Required sequence to write to EEPROM                *
; *        Used by update_EEPROM and calibrate routines            *
; *                                        *
; *******************************************************************************
;
write_EEPROM
    bsf    EECON1,WREN    ; Set the EEPROM write enable bit
        ; Start of required sequence
    movlw    0x55        ; Write 0x55 and 0xAA to EECON2
    movwf    EECON2        ; control register, as required
    movlw    0xAA        ;   
    movwf    EECON2        ;
    bsf    EECON1,WR    ; Set WR bit to begin write
        ; End of required sequence
bit_check
    btfsc    EECON1,WR    ; Has the write completed?
    goto    bit_check    ; No, keep checking
    bcf    EECON1,WREN    ; Clear the EEPROM write enable bit
    incf    EEADR,f        ; Increment the EE write address
    return            ; Return to the caller
;
; *******************************************************************************
; *                                        *
; * Purpose:    Read a byte of EEPROM data at address EEADR into EEDATA.    *
; *                                        *
; *   Input:    The address EEADR.                        *
; *                                        *
; *  Output:    The value in EEDATA.                        *
; *                                        *
; *    NOTE:    All in BANK 1                            *
; *                                        *
; *******************************************************************************
;
read_EEPROM
;
    bsf    EECON1,RD    ; Request the read
    movf    EEDATA,W    ; Get the data
    incf    EEADR,f        ; Increment the read address
    return            ; Return to the caller
;                                                                             
; *******************************************************************************
; *                                        *
; * 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 AD9850/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    ; Get the "normal" freq_0 term
    addwf    AD9851_1,f    ; 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    ; Get the "normal" freq_0 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    ; Get the "normal" 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    ; Get the "normal" 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

#ifdef     AD9850
    movlw    0x00        ; No clock multiplier (AD9850)             
#endif

#ifdef     AD9851
    movlw    0x01        ; Turn on 6x clock multiplier (AD9851)     
#endif

    movwf    AD9851_4    ; Last byte to be sent                     
                ; Mult answer is in bytes _3 .. _0         
    return            ; Done.
;
; *******************************************************************************
; *                                        *
; * Purpose:    This routine sends the AD9850/AD9851 control word to the DDS    *
; *        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 Least Significant Byte       
    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    DDS_dat        ; No, send one
    nop                             
    bsf    DDS_clk        ; Toggle write clock
    nop
    bcf    DDS_clk        ;                                           
    goto    break        ;
send0
    bcf    DDS_dat        ; Send zero
    nop                                 
    bsf    DDS_clk        ; Toggle write clock
    nop
    bcf    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    DDS_load    ; Send load signal to the AD9850/AD9851
    nop
    bcf    DDS_load    ;
    nop
    bcf    DDS_dat        ;
    nop
    bcf    DDS_clk        ;
    return            ;
;
; *******************************************************************************
; *                                        *
; * Purpose:    Wait delays.                            *
; *                                        *
; *                                        *
; *   Input:    None                                *
; *                                        *
; *  Output:    None                                 *
; *                                        *
; *******************************************************************************
;
wait_100us
    movlw    D'1'
    movwf    timer2
    movlw    D'31'
    movwf    timer1
    goto    loop

wait_1ms
    movlw    D'2'
    movwf    timer2
    movlw    D'73'
    movwf    timer1
    goto    loop

wait_10ms
    movlw    D'13'
    movwf    timer2
    movlw    D'251'
    movwf    timer1

loop   
    decfsz    timer1,f
    goto    loop
    decfsz    timer2,f
    goto    loop
    return
;
        END
;--------------------------------------------------------------------------------
 

Ian Rogers

User Extraordinaire
Forum Supporter
Most Helpful Member
For the compiler, we are using MPlabX 5.45, XC8 and a 12F1840 PIC.
Oooh my favorite pic... What I did for Nigels asm tutorials is a like for like asm -> C translation so members could follow the code easily..

If I did this with your code it would be a quarter of the size..
 

Nigel Goodwin

Super Moderator
Most Helpful Member
Oooh my favorite pic... What I did for Nigels asm tutorials is a like for like asm -> C translation so members could follow the code easily..

If I did this with your code it would be a quarter of the size..
But what about after compilation/assembly :D

I too like the 12F1840, but also the newer 16F18313 which has more of the new enhanced peripherals.
 

Pommie

Well-Known Member
Most Helpful Member
OK, here's a start just doing your defines (note L on end to denote 32 bits) and you initial setup code'
Code:
#include <xc.h>
#include <stdint.h>

// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection->INTOSC oscillator: I/O function on CLKIN pin
#pragma config WDTE = SWDTEN    // Watchdog Timer Enable->WDT controlled by the SWDTEN bit in the WDTCON register
#pragma config PWRTE = OFF      // Power-up Timer Enable->PWRT disabled
#pragma config MCLRE = ON       // MCLR Pin Function Select->MCLR/VPP pin function is MCLR
#pragma config CP = OFF         // Flash Program Memory Code Protection->Program memory code protection is disabled
#pragma config CPD = OFF        // Data Memory Code Protection->Data memory code protection is disabled
#pragma config BOREN = ON       // Brown-out Reset Enable->Brown-out Reset enabled
#pragma config CLKOUTEN = OFF   // Clock Out Enable->CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin
#pragma config IESO = ON        // Internal/External Switchover->Internal/External Switchover mode is enabled
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable->Fail-Safe Clock Monitor is enabled

// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection->Write protection off
#pragma config PLLEN = ON       // PLL Enable->4x PLL enabled
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable->Stack Overflow or Underflow will cause a Reset
#pragma config BORV = LO        // Brown-out Reset Voltage Selection->Brown-out Reset Voltage (Vbor), low trip point selected.
#pragma config LVP = OFF        // Low-Voltage Programming Enable->High-voltage on MCLR/VPP must be used for programming

#define AD9850        // Using the AD9850
//#define AD9851        // Using the AD9851

#ifdef AD9850
//    For 125 MHz Oscillator =======
#define ref_osc  0x225C17D0L
#endif
#ifdef AD9851
//    For 180 MHz (30 MHz clock and 6x multiplier)                              
#define ref_osc 0x17DC65DEL
#endif

//Limit contains the upper limit frequency as a 32 bit integer.
#define limit 0x0053EC60L

//Low_limit contains the lower frequency limit as a 32 bit integer
#define limit_low 0x004C4B40L

//    Default contains the default startup frequency as a 32 bit integer.
//    This will be overwritten by the frequency save routine in normal use.
#define default 0x004C4B40L

//    Frequency at which Calibration will take place
#define cal_freq 0x00501BD0L

void main(void){
    PORTA=0;
    TRISA=0b00111100;
    ANSELA=0;   
    // SCS FOSC; SPLLEN disabled; IRCF 8MHz_HF;
    OSCCON = 0x70;      //set osc to 32MHz
    BORCON = 0x00;
    while(PLLR == 0);
    // WDTPS 1:65536; SWDTEN OFF;
    WDTCON = 0x16;
    WPUA = 0x00;
    OPTION_REGbits.nWPUEN = 1;

    while(1){
    }
}
You could also have done your defines as
Code:
#define AD9850       

#ifdef AD9850
//    For 125 MHz Oscillator =======
#define ref_osc  0x225C17D0L
#else AD9851
//    For 180 MHz (30 MHz clock and 6x multiplier)                               
#define ref_osc 0x17DC65DEL
#endif
Mike.
 

Pommie

Well-Known Member
Most Helpful Member
I looked through your code and saw the send_DDS_word routine and note it's 40 lines long, I converted this and this is the result,
Code:
/*
This sends a word to the DDS module
call by doing sendDDSword(16 bit value to send);
*/
void sendDDSword(int dat){
    char i;
    for (i=0;i<16;i++){     //a for loop of 16 times
        if(dat&1 == 1){     //is bit 0 set
            DDSdata=1;      //yes so set the data line high
        }else{
            DDSdata=0;      //no so set it low
        }
        DDSclock=1;         //clock out the  data
        DDSclock=0;
        dat>>=1;            //shift the data right for next bit
    }
    DDSload=1;
    DDSload=0;
}
Is this what you had in mind and do you understand what it's doing? And, how it's doing it?
Do you understand how indenting does nothing but make it readable?

Mike.
I normally use the standard int definitions (uint8_t uint16_t) but have stuck to int and char to keep it simple.
Edit, forgot, you need to add the defines for the pins and a prototype at the top.
Code:
#define DDSdata AN0
#define DDSclock AN2
#define DDSload AN1

void sendDDSword(uint16_t);
Edit2, missed the load toggle bit, added now
 
Last edited:

Pommie

Well-Known Member
Most Helpful Member
And, just realized you're sending 32 bits. You can try changing the above to do 32 bits. Note a long is a 32 bit variable.

Mike.
 
Give me a bit to catch up Mike, I ended up having to go out again and haven't even had the chance to work out how to set up a project in MPlabX yet.

Quick nosey at the first part of the code (post #4) - OK with that until I get to - while(PLLR == 0); - don't explain it yet, give me a chance to try and nut it out.

As to the length of some of the asm stuff, yes I know it's longer than necessary, but it kind of got left on the back burner when other things took over my time.
 
Ok, I assume that while(PLLR == 0); means the code will sit there until the PLL flag indicates the PLL is up to speed ?

And - for (i=0;i<16;i++) --> for (i=0;i<32;i++) to set up for 32 bit transfer.

Can items such as OSCCON = 0x70; be set up as OSCCON = 0b0100 0110 - my brain stopped working in 16 bit mode years ago - I find it quicker and easier to relate back to the datasheet in 0b0100 0110 format.

#define DDSdata AN0
#define DDSclock AN2
#define DDSload AN1
void sendDDSword(uint32_t);
You say at the top - top of what? Before or after the pragma's, after the Define AD9850/51 code? - This some of the small stuff that confuses me.
 
Last edited:

Pommie

Well-Known Member
Most Helpful Member
You say at the top - top of what? Before or after the pragma's, after the Define AD9850/51 code?
It can be either but I just put it after the #define stuff.

The setup code is simply changing,
Code:
   movlw    b'00111100'    ; GPIO 2,3,4,5 inputs 0,1 outputs
    movwf    TRISIO        ;
To,
Code:
    TRISIO=0b00111100;
Just a different way to write the same thing.

Mike.
 

Pommie

Well-Known Member
Most Helpful Member
OK, before we get any further, where did this come from - nWPUEN - I know it is the pull-up disable, but where did the 'n' on the front come from?
It's one of microchips defines (not weak pullup enable) and to find what bits are contained in a register, type OPTION_REGbits. and a list will appear, like this,
WPUEN.png
Once you know what the bit is called you can just use the bit name. I.E. nWPUEN = 1.

Got to go out now (jab time) but happy to answer any questions you may have later. And I noticed a typo in some earlier code I posted.

Mike.
 

Pommie

Well-Known Member
Most Helpful Member
I made a mistake in the sendDDSword routine. I would normally do if(dat&1) as anything not zero is seen as true. However, for clarity I used if(dat&1==1) which will compare 1 to 1 and then and with dat. To correct this it needs changing to if((dat&1)==1).

When you mentioned C not being standard, it occurred to me that you maybe mistaking different styles,
All these will do the same thing,
Code:
1.      if((dat&1) == 1){   //is bit 0 set
            DDSdata=1;      //yes so set the data line high
        }else{
            DDSdata=0;      //no so set it low
        }

2.      if((dat&1) == 1)    //is bit 0 set
            DDSdata=1;      //yes so set the data line high
        else
            DDSdata=0;      //no so set it low

3.      if((dat&1) == 1)       //is bit 0 set
        {
          DDSdata=1;          //yes so set the data line high
        }    
        else
        {
          DDSdata=0;          //no so set it low
        }
  
4.     DDSdata=(dat&1)?1:0;
The first 3 are just different styles, with and without curly braces and with a separate line for the curly brace.
Note if curly braces are omitted then only ONE instruction can follow the if or else. This is considered bad practice as someone in the future might not notice the lack of braces and insert a second instruction.

The fourth version is one that is not often used as a lot of people don't understand what it does. Best just avoid this version.

Mike.
 
It just appears that what ever different C compiler and associated examples I looked at in the past, it was done a different way every time, whether that was just one particular persons way of doing something, I don't know, but it made (and still does), following any of the billions of bits of code out there hard to digest.

And the brain is still trying to divorce me today as I have been looking at various bits and pieces trying to work out how move on to the next stage with this.

In my ASM file, there are all the nice neat (well, maybe not so tidy), seperate blocks of code that do their own thing when called up from the main loop, but I have not found an easily digestible example of how to do that in C.

I found the book "Programming PIC Microcontrollers with XC8" today and the examples in there have only a passing resemblance to what has been done here so far.

Beginning to think I need the 'Ultimate Idiots guide to C for Complete Idiots'.
 

Nigel Goodwin

Super Moderator
Most Helpful Member
In my ASM file, there are all the nice neat (well, maybe not so tidy), seperate blocks of code that do their own thing when called up from the main loop, but I have not found an easily digestible example of how to do that in C.
That's exactly what C does - almost everything is split into 'functions', such as the function shown in post #5. Much simpler than doing it in assembler, where it's all sub-routines - a function allows you to pass variables to it, and to (optionally) return a result. Essentially you're creating your own language, with functions been the extra commands. Forth was like that as well - with any Forth program been essentially one single word.

Incidentally, other languages (like Pascal) provide both functions and procedures - where a procedure accepts variables, but doesn't return anything - but C uses functions for both, with the addition of 'void'.
 
That's fine, but how do you call any particular function and then get back from it and then pass the information it's gathered back to the main loop? - that is the info I'm looking for in simple to understand english.
 

Ian Rogers

User Extraordinaire
Forum Supporter
Most Helpful Member
Okay..

Function.... Takes several arguments.. Returns an argument... Explanation

Code:
int  someAddFunction( int a, int b)
   {
   return a + b;
   }

void main()
   {
    int result = 0;

   result = someAddFunction( 6, 5);
   }
C uses a software stack... This enables you to preset several items before the call, just as you do in asm... Here however you are not tied to global variables

The functions address will be on the stack, then the two arguments... The result is normally returned the same way.. Unless its a char, then its the Acc.

This way, you are not reliant on the crappy 8 level hardware stack and your calls can go much deeper...
 

Pommie

Well-Known Member
Most Helpful Member
As you use EEPROM in your code, at some time we'll need functions to write it.
The datasheet gives this example,
EEPROM.png
To convert this we make a function, We'll call it readEEPROM and we'll give it two bytes of data, the address and the data.
We use, void readEEPROM(unsigned char address,unsigned char data) - void means nothing is returned and unsigned char is a byte variable (address and data).

We can now almost convert the above code action (not line) by action.
The first three lines will convert to EEADRL=address;
The second two to EEDATL=data;

The final code will look like,
Code:
void writeEEPROM(unsigned char address,unsigned char data){
    EEADRL = address;
    EEDATL=data;
    CFGS=0;
    EEPGD=0;
    WREN=1;
    EECON2=0x55;
    EECON2=0xAA;
    WR=1;
    WREN=0;
    while(WR==1);
}
As I've placed this code at the end of the code I need to add a prototype to the top.
The top of the code now looks like,
Code:
#define cal_freq 0x00501BD0L

#define DDSdata AN0
#define DDSclock AN2

void sendDDSword(int);
void writeEEPROM(unsigned char,unsigned char);

void main(void){
To test the function I added a call to it just before the while(1),
Code:
    WPUA = 0x00;
    writeEEPROM(0,0x23);
    while(1){
    }
I can compile this and it will run. I can then stop the debugger (pickit4 in my case) and read the contents of the chip.
I then select the EE data memory tab and see,
ee-data.png
And I know that it's working.

You can try this for yourself.

Mike.
Edit, I missed out the GIE stuff as I don't have an IRQ yet and didn't want to tempt fate.
 
Last edited:

Nigel Goodwin

Super Moderator
Most Helpful Member
That's fine, but how do you call any particular function and then get back from it and then pass the information it's gathered back to the main loop? - that is the info I'm looking for in simple to understand english.
I don't think the previous two answers have really addressed your question?.

In Ian's example:

int someAddFunction( int a, int b)
{
return a + b;
}
This is a 'function', and the first 'int' which I've highlighted is what the reply is returned as - an integer. I've also highlighted 'return' which is how it returns the answer in that integer.

The rest of Ian's answer shows how to use it:

int result = 0;
result = someAddFunction( 6, 5);
Simply declare a variable (result) as an integer, and then transfer the answer from the function to it using '='.

Pommie's example is really a 'procedure' as it doesn't return anything:

void writeEEPROM(unsigned char address,unsigned char data){
EEADRL = address;
EEDATL=data;
CFGS=0;
EEPGD=0;
WREN=1;
EECON2=0x55;
EECON2=0xAA;
WR=1;
WREN=0;
while(WR==1);
}
So the 'return' variable at the beginning (highlighted again) is declared as 'void' (i.e. doesn't exist), and there's no return instruction, as there's nothing to return. So the routine simply 'runs out of the bottom', and goes back to where it came from - a sub-routine in assembler.
 

Latest threads

EE World Online Articles

Loading
Top