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
;--------------------------------------------------------------------------------