augustinetez
Active Member
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).
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).
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
;--------------------------------------------------------------------------------