Here is the source code for the PIC Controller. Please observe the site policy on the home page when copying or using it.
; TITLE 'QRP2000 VFO control for dual AD9850 DDS chips, G7PUB'
LIST P=16C84,F=inhx8m
;---------------------------------------------------------------------
; Program: QRP2000.ASM
;
; SUMMARY
; -------
;
; The 'QRP2000' receiver uses a PIC 16c84 microcontroller programmed
; with this software to control a pair of AD9850 DDS chips. The DDS
; chips act as a single VFO with two outputs in phase quadrature.
;
; Frequency display is provided by a Hitachi LM016 (or similar) LCD.
; User control is by a 12-key (3 x 4) keypad with fine tuning via
; an optical encoder.
;
; This design forms the basis of a very stable general coverage
; receiver.
;
; Copyright (c) MPC Data Limited, 1998
;
; LICENSE CONDITIONS
; ------------------
;
; Redistribution and use in source and binary forms, with or without
; modification, are permitted provided that the following conditions
; are met:
; 1. Redistributions of source code must retain the copyright notice,
; this list of conditions and the following disclaimer.
; 2. Redistributions in binary form must reproduce the copyright notice,
; this list of conditions and the following disclaimer in the documentation
; and/or other materials provided with the distribution.
; 3. All advertising materials mentioning features or use of this
; software must display the following acknowledgement:
; This product includes software developed by MPC Data Limited,
; Bradford-on-Avon, England
;
; DISCLAIMER
; ----------
;
; THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
; IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
; OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
; IN NO EVENT SHALL MPC DATA LIMITED OR THE AUTHOR BE LIABLE FOR ANY DIRECT,
; INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
; HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
; STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
; ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
; OF SUCH DAMAGE.
;
; TECHNICAL OUTLINE
; -----------------
;
; The AD9850 DDS chip needs a 40 bit value clocked in LSB first.
; D0 to D31 is a 32-bit frequency 'increment' (see below).
; D32, D33 and D34 are always 0. D35 to D39 determine the phase. To
; give quadrature outputs we program DDS-a with a phase of 0 and DDS-b
; with a phase of 1000b.
;
; The frequency increment determines the frequency in increments of
; (DDS clock freq) / 2 ^ 32. We are clocking the DDS at 100MHz so the
; frequency increment is 100MHz / 2 ^ 32 i.e. approx 0.023 Hz.
;
; The software can optionally also control the selection of a band-pass
; input filter, thus offering complete receiver control. If the symbol
; BAND_SWITCH is defined, the frequency is compared to the centre
; frequency of each amateur HF band +/- 10% - if the required frequency
; falls in this range then the relevant input filter is selected, and
; if not a general low pass filter is selected instead. The filters
; are selected by raising one of 10 (9 bands plus 'general') outputs
; on a 4-to-16 line decoder/latch. See below for details of the
; hardware mapping.
;
; The optical rotary encoder used for fine tuning gives about 80 clock
; pulses per revolution. At a nominal resolution of 50Hz for each clock
; pulse it would take a very large number of revolutions to tune over the
; full 30MHz range. To reduce the time taken to tune with the optical
; encoder, this s/w optionally provides simple variable-rate tuning. When
; the shaft is turning at less than 0.7 rev (56 pulses) per second, we tune
; in 50Hz steps. By the time that tuning has speeded up to 2.5 rev/sec, the
; step size increases to 1000Hz for each pulse.
;
; It's hard to achieve variable tuning 'cleanly' without using a lot
; of processor time. This implementation uses a rather crude hard-coded
; approach to the problem. While no pulses are received we count down
; from 15. At a loop execution speed of 1.2ms (approx) we get down to
; zero in about 18ms, corresponding to a tuning rate of one rev per
; (18 * 80)ms, i.e. roughly one rev every 1.5 seconds. By dividing the
; count-down variable by 4 at the point that a clock edge occurs, we
; have a tuning rate indication in the range 0 (slow) to 3 (fast).
;
; NOTE: The clock pulses from this particular encoder are very short
; (typically 10us), so the encoder clock line is connected to the PIC's
; RB0/INT pin. This enables us to register the pulses in a simple ISR,
; although all the processing is still done in the main application loop.
; We have to disable interrupts whilst using RB0 as an output (see below)
; because transitions caused by the PIC still trigger interrupts!
;
;
; I/O Configuration
; -----------------
;
; PIC pins double up for LCD and DDS control by only selecting one
; device at a time.
;
; LCD Pinouts:
; Port B 0-7 LCD data
; Port A 2 Instruction/data switch
; Port A 4 LCD enable
; (LCD R/W is permanently set to 'write'. This means that we cannot
; check for LCD busy - we just rely on suitable delays.
;
; DDS Pinouts:
; Port A 0 Freq. Update for DDS-a and DDS-b
; Port A 1 Data (DDS-a)
; Port A 2 Data (DDS-b)
; Port A 3 Clock (DDS-a and -b)
;
; Frequency Band Selection Lines (optional):
; Port B 4 - 7 4-bit selection value, range 0 - 15. These are decoded
; Port A 0 by a 4-to-16 decoder which is latched by line A0 - i.e.
; the same line that triggers DDS freq. update.
; Decoder outputs 1 to <BANDS> select receiver input filters
; for the frequencies defined below, output <BANDS+1>
; selects a 'by-pass' for frequencies which are outside
; any of the bands, and output 16 causes a hardware reset
; of the DDS chips.
;
; Misc I/O:
; Port B 0 - 4 Inputs: 2 optical encoder
; 3 inputs from key array
; Port B 5 - 7 Outputs: 3 outputs for scanning key array
; Port A 2 Output: 4th output for scanning key array
;
; (Note 4 x 3 key array gives 12 keys, including numeric 0 - 9 allowing
; direct entry of frequency).
;
; At present, the following keys are defined:
; Key Legend Action
; 0 Start direct frequency entry (# or * to finish)
; 1 Step Up 50Hz
; 2 Step Up 5KHz
; 3 Step Up 500KHz
; 4 Step Down 50Hz
; 5 Step Down 5KHz
; 6 Step Down 500KHz
; 7 Toggle upper/lower sideband
; 8 Store memory (set memory <n> to current freq.)
; 9 Recall memory (set frequency from memory <n>)
; # Currently unused (reserved for future enhancements)
; * Function key: (NOT YET IMPLEMENTED)
; *1 Key tranmitter on/off (toggle) (NOT YET IMPLEMENTED)
; *2 Transmit power increase (NOT YET IMPLEMENTED)
; *3 Transmit power decrease (NOT YET IMPLEMENTED)
; *4 ATU auto-adjust (NOT YET IMPLEMENTED)
; *5 Audio filter mode (toggle) (NOT YET IMPLEMENTED)
;
; Author: Alan Rowe, G7PUB
;
; Revision History:
; 19/06/1998 V0.1 First attempt.
; 30/09/1998 V0.2 Added support for USB/LSB selection
; 21/10/1998 V0.3 Jan Verduyn, G0BBL, added power-up delay whilst
; DDS chips get h/w reset, and temporarily fixed
; tuning step for optical encoder to 50Hz.
; 17/11/1998 V0.4 Alan Rowe, G7PUB, added support for band filter
; selection, provide DDS chip reset under s/w control,
; and inverted logic levels for keyboard scanning.
; 01/12/1998 V0.5 Alan Rowe, G7PUB, reduce LCD display update rate
; because we lose optical encoder pulses whilst the
; display is being updated ('cos PORTB lines are made
; outputs).
; 05/12/1998 V0.6 Alan Rowe, G7PUB, added direct frequency entry facilty
; and re-organised key pad function assignments.
; 11/01/1999 V0.7 Alan Rowe, G7PUB. New approach which handles
; mouse-style rotary encoders quite separately from
; more sophisticated 'clock + direction' type encoders.
; Mouse-style encoders are polled, and any change on
; *either* line generates an update. This enables us to
; get twice the resolution which we could get previously.
; Very clever algorithm for determining direction was
; devised by Jan Verduyn, G0BBL.
; 17/01/1999 V0.8 Alan Rowe, G7PUB. Added support for CW mode as well
; as existing USB/LSB, and finished implementing 10
; non-volatile memories for favorite frequencies/modes.
; 22/01/1999 V0.9 Alan Rowe, G7PUB. Offset tuned frequency by +/- 1.5KHz
; so that the frequency of the signal is displayed,
; rather than the (notional) carrier frequency.
; 29/01/1999 V1.0 Alan Rowe, G7PUB. Read reference oscillator value
; from EEPROM so that it can be set to exactly match
; the crystal frequency for each individual rig.
;---------------------------------------------------------------------
; Definition of assembly-time options.
; NOTE: there may not be enough program space on the PIC16C84 to
; accomodate all combinations of options - for example, enabling
; SSB_OFFSET may require VARI_TUNE to be disabled to make enough room.
#define MOUSE_STYLE 1 ; Define this symbol if using a 'mouse-type'
; rotary encoder (i.e. raw optical pulse
; trains in quadrature) rather than the
; more complex 'clock + direction' style
; of encoder.
#define VARI_TUNE 1 ; Define this symbol to enable variable-rate
; tuning via the rotary encoder. This is
; a matter of personal preference.
#define BAND_SWITCH 1 ; Define this symbol if mixer is preceded
; by input filters for different bands. The
; filters are assumed to be selected via
; a pair of latches (see above).
; #define SSB_OFFSET 1 ; Define this symbol to offset the DDS tuning
; frequency by +/- 1.5KHz when in USB or LSB
; mode. This has the effect that the displayed
; frequency indicates the centre of the
; sideband, not the (non-existent) carrier.
; Normally disabled, since this is unusual
; for an amateur receiver.
; Define PIC special function regs. in bank 0
INDF EQU 0
RTCC EQU 1
PC EQU 2
STATUS EQU 3
FSR EQU 4
PORTA EQU 5
PORTB EQU 6
EEDATA EQU 8
EEADR EQU 9
PCLATH EQU 0A
INTCON EQU 0B
; Define PIC special function regs. in bank 1
OPTREG EQU 1
EECON1 EQU 8
EECON2 EQU 9
; Define status reg. bits of interest
#define _C STATUS,0
#define _Z STATUS,2
#define RP0 STATUS,5 ; Register bank select
; Define relevant interrupt control flags
#ifndef MOUSE_STYLE
#define GIE INTCON,7 ; Global interrupt enable bit
#define INTE INTCON,4 ; RB0/INT interrupt enable bit
#define INTF INTCON,1 ; RB0/INT flag
#endif
; Bit masks for the OPTION register
PULLUP_MASK EQU 80 ; Value for OPTION reg to disable pull-ups
INTEDG_MASK EQU 40 ; Value for OPTION reg to select which edge
; INT0 triggers on.
; Define relevant EEPROM control flags
#define RD EECON1, 0
#define WR EECON1, 1
#define WREN EECON1, 2
; Define PIC I/O line usage
#define LCD_ID PORTA, 2 ; RA2 is LCD instruction/data switch
#define LCD_EN PORTA, 4 ; RA4 is enable for LCD controller
#define LCD PORTB ; PORT B is output to LCD controller
#define DDS_FQ_UD PORTA, 0 ; Freq. Update for DDS-a and -b
#define DDS_DATA_A PORTA, 1 ; Data (DDS-a)
#define DDS_DATA_B PORTA, 2 ; Data (DDS-b)
#define DDS_CLOCK PORTA, 3 ; Clock (DDS-a and -b)
#ifdef MOUSE_STYLE
#define OPT_PORT PORTB
#define OPT_MASK 03 ; Mask to separate optical encoder inputs
#define OPT_1 0 ; Optical pulse trains on lines 0 + 1
#define OPT_2 1 ; of PORTB
#else
#define OPT_CLK PORTB, 0 ; Optical encoder 'clock' signal
#define OPT_DIR PORTB, 1 ; Optical encoder direction indicator
#endif
#define KEY_IN1 PORTB, 2 ; Keyboard input
#define KEY_IN2 PORTB, 3 ; Keyboard input
#define KEY_IN3 PORTB, 4 ; Keyboard input
#define KEY_OUT1 PORTA, 2 ; Keyboard output
#define KEY_OUT2 PORTB, 7 ; Keyboard output
#define KEY_OUT3 PORTB, 6 ; Keyboard output
#define KEY_OUT4 PORTB, 5 ; Keyboard output
#define KEY_IO_MASK 1F ; Port B tristate mask for keypad + opto input
#define KEY_IN_MASK 1C ; Mask for keypad data input bits
; LCD command values
#define LCD_CLEAR 01 ; Clear display, home cursor
#define LCD_8BIT 38 ; 8-bit interface, 2 display lines
#define LCD_DISABLE 08 ; Disable entire display
#define LCD_ENABLE 0C ; Display enable, no cursor
#define LCD_MODESET 04 ; Increment cursor, no scrolling
#define LCD_SETADDR 80 ; Set display address for writing
#define LCD_CURSOFF 0C ; Cursor off
#define LCD_CURSON 0F ; cursor on
#define LCD_HOME 02 ; send cursor home
#define LCD_CURSRIGHT 14 ; move cursor right one space
; Keycode definitions for 'funny' keys (NOTE: '1' - '9' generate numeric
; value which is the same as the key legend).
#define STAR_KEY .10 ; Key code for * key
#define ZERO_KEY .11 ; Key code for '0' key
#define HASH_KEY .12 ; Key code for # key
; Keypad action assignments (see header comment for description)
#define UP_50HZ .1
#define UP_5KHZ .2
#define UP_500KHZ .3
#define DOWN_50HZ .4
#define DOWN_5KHZ .5
#define DOWN_500KHZ .6
#define LSB_USB .7
#define MEM_STORE .8
#define MEM_RECALL .9
#define DIR_FREQ .0 ; Causes start of direct frequency entry
#define NO_KEY .99 ; Indicates no key is currently pressed
; Initial auto-repeat rate for keypad entry. With an idle loop time
; of slightly over 1ms, the maximum value of FFh gives an initial
; auto-repeat time of about 1/3 sec.
#define RPT_INIT 0FF
; Constants relating to variable-rate tuning via optical encoder:
#define MAX_STEP_SIZE 4 ; Allow 4 different step sizes
#define COUNT_DIVISOR 4 ; Divide countdown timer by 4 to give indication
; of tuning rate.
#define COUNT_INIT ((MAX_STEP_SIZE * COUNT_DIVISOR) - 1)
; Minimum delay in ms between successive updates to the LCD display (approx.)
#define LCD_UPDATE_INTERVAL .100
; Define expected DDS clock speed, and EEPROM location to find the *exact*
; clock speed (so that value can be tuned to precisely match the crystal
; used).
DDS_CLOCK_SPEED equ .100000000
REF_XTAL_LOC equ .10 ; Locations 0 - 9 used for 'favorite' freqs.
; Define frequency to tune to at power-up, and (approx.) maximum
; permitted frequency.
START_FREQ equ .7000000
MAX_FREQ equ .30000000 ; (Actually allows tuning somewhat higher)
; Define frequency offset from notional carrier frequency to the centre
; of a sideband (for SSB) or from the carrier to the tuning value needed
; to give a sensible audio pitch (for CW)
#ifdef SSB_OFFSET
SIDEBAND_OFFSET equ .1500
#endif
CWTONE_OFFSET equ .800
; Define frequencies for switching input band filters. Assume that
; the filters are peaked for the centre of each of the HF amateur bands,
; and cover a range of +/- 10% from that value.
; NOTE: the values here assume the UK band plan, and will need minor
; changes for other countries.
#ifdef BAND_SWITCH
BAND1 equ ((.1819000 + .2000000) / 2) ; 160m [ Calculate band centre in
BAND2 equ ((.3500000 + .3800000) / 2) ; 80m [ Hz, based on upper and lower
BAND3 equ ((.7000000 + .7100000) / 2) ; 40m [ frequency limits for that
BAND4 equ ((.10100000 + .10150000) / 2) ; 30m [ band.
BAND5 equ ((.14000000 + .14350000) / 2) ; 20m
BAND6 equ ((.18068000 + .18168000) / 2) ; 17m
BAND7 equ ((.21000000 + .21450000) / 2) ; 15m
BAND8 equ ((.24890000 + .24990000) / 2) ; 12m
BAND9 equ ((.28000000 + .29700000) / 2) ; 10m
BANDS equ .9 ; Total number of bands defined
#endif
;PIC register usage:
delayhi equ .12 ; Counters used for delays
delaylo equ .13
count equ .14 ; Used by freq. display routines
tmp equ .15 ; Misc. 8-bit scratch register
; acca to accc are 16-bit scratch storage for maths functions, treated as
; 16-bit storage areas, with MSB in the lower address.
acca equ .16
accb equ .18
accc equ .20
accd equ .22
acce equ .24
; dacca to dacce are 32-bit scratch registers for double-length divide
; function. WARNING these overlay the 16-bit scratch registers AND
; digit1 to digit8, below.
dacca equ .16
daccb equ .20
daccc equ .24
daccd equ .28
dacce equ .32
; Eight digits of frequency after conversion to BCD (we only need up
; to 30,000000). NOTE: some code assumes that these registers are
; located in ascending sequence.
digit1 equ .26
digit2 equ .27
digit3 equ .28
digit4 equ .29
digit5 equ .30
digit6 equ .31
digit7 equ .32
digit8 equ .33
; 32-bit variable to hold current frequency value
freq equ .36
lastkey equ .40 ; Last key pressed on the keypad
rptcount equ .41 ; Counter for keypad auto-repeat function
speed equ .42 ; Approx. speed indication for optical encoder
LCDtimer equ .43 ; Timer for deferred display updates
flags equ .44 ; Single-bit status flags...
last equ .45 ; Last state of rotary encoder inputs
#ifndef MOUSE_STYLE
#define opticlk flags,0 ; Flag set when we see optical encoder clock pulse
#endif
#define optidir flags,1 ; Flag set when tuning UP
#define usbflag flags,6 ; Flag set if upper sideband selected (clear = LSB)
#define USB_TOGGLE 40 ; Mask to toggle usbflag
#define cwflag flags,7 ; Flag set to indicate CW mode (also requires usbflag
; to be set since CW is handled almost the same as USB)
#define MODE_MASK 0C0 ; Mask to extract mode flags from others
;
; Define power-up entry point
;
org 0
goto start
;**********************************************************************
;
; Interrupt routine - INT0 interrupt is used to detect changes in the
; output of the optical encoder. For the more complex 'clock+direction'
; style of encoder we need to read it via this interrupt routine because
; the clock pulses are quite short (typically 10us) so if we try and
; poll the clock line we miss most of the pulses.
;
; For mouse-style encoders we just poll for changes of state on
; the input lines, and this ISR is not used.
;
;**********************************************************************
#ifndef MOUSE_STYLE
org .4
bsf opticlk ; Signal clock edge was seen
bsf optidir
btfsc OPT_DIR ; Set direction flag as read from encoder
bcf optidir
bcf INTF ; Clear interrupt flag
retfie ; Return, re-enabling global interrupts
#else
org .5
#endif
;**********************************************************************
;
; Lookup tables for optical encoder speed -> step size conversion.
; Because of the method used to perform the table look-up, these
; tables have to be in the first page (256 words) of program memory.
;
;**********************************************************************
#ifdef VARI_TUNE
OptSpdHi
addwf PC, F
retlw HIGH .50
retlw HIGH .100
retlw HIGH .200
retlw HIGH .500
OptSpdLo
addwf PC, F
retlw LOW .50
retlw LOW .100
retlw LOW .200
retlw LOW .500
;**********************************************************************
;
; getstep
; -------
;
; Routine to convert tuning speed estimate to step size.
; (Also resets the speed indicating variable)
; Note: uses lookup tables rather than bit shifting to
; determine step size to make it easier to modify the values.
;
; Input: speed variable contains estimate of tuning rate
;
; Output: dacca contains corresponding step size in Hz
;
;**********************************************************************
getstep:
rrf speed, F
rrf speed, W ; Divide speed by 4 to get step
andlw 0x03
movwf tmp ; (Save a copy of step table index)
clrf dacca
clrf dacca+1
call OptSpdHi ; Look-up high byte of step size
movwf dacca+2
movf tmp, W ; Restore step table index
call OptSpdLo ; ..and look-up low byte of step size
movwf dacca+3
movlw COUNT_INIT
movwf speed
retlw 0
#endif
; Lookup tables for optional band-pass filter selection. If the frequency
; is within +/- 10% of the filter centre frequencies defined above, then
; the relevant filter is selected.
#ifdef BAND_SWITCH
; Scale factor needed to ensure that the band filter limits
; will all fit into a byte-wide table. We use a power of 2
; to reduce the time taken to determine if a given frequency
; is within range of a band filter.
SCALE_FACTOR equ 20000 ; 20000 hex = 2 ^ 17
; Note - getlatch() makes assumptions about
; this value - if it is changed then getlatch
; must be changed to match.
lookupBandLow:
addwf PC, F
retlw ((BAND1 * .9) / .10) / SCALE_FACTOR
retlw ((BAND2 * .9) / .10) / SCALE_FACTOR
retlw ((BAND3 * .9) / .10) / SCALE_FACTOR
retlw ((BAND4 * .9) / .10) / SCALE_FACTOR
retlw ((BAND5 * .9) / .10) / SCALE_FACTOR
retlw ((BAND6 * .9) / .10) / SCALE_FACTOR
retlw ((BAND7 * .9) / .10) / SCALE_FACTOR
retlw ((BAND8 * .9) / .10) / SCALE_FACTOR
retlw ((BAND9 * .9) / .10) / SCALE_FACTOR
lookupBandHi:
addwf PC, F
retlw ((BAND1 * .11) / .10) / SCALE_FACTOR
retlw ((BAND2 * .11) / .10) / SCALE_FACTOR
retlw ((BAND3 * .11) / .10) / SCALE_FACTOR
retlw ((BAND4 * .11) / .10) / SCALE_FACTOR
retlw ((BAND5 * .11) / .10) / SCALE_FACTOR
retlw ((BAND6 * .11) / .10) / SCALE_FACTOR
retlw ((BAND7 * .11) / .10) / SCALE_FACTOR
retlw ((BAND8 * .11) / .10) / SCALE_FACTOR
retlw ((BAND9 * .11) / .10) / SCALE_FACTOR
;**********************************************************************
;
; getlatch
; --------
;
; Determines the latch values needed to select the correct band-pass
; input filter for the current frequency. See file header comments
; for more detail.
;
; Input: 'freq' contains current frequency as 32-bit value (in Hz)
;
; Output: 'count' holds latch output value to select the correct filter.
;
;**********************************************************************
getlatch:
; Scale down frequency to match values in band filter frequency
; tables. NOTE: assumes that table entries are scaled by 2^17 - if
; the scale factor changes, this routine will need to be altered.
movf freq, W
movwf acca
movf freq+1, W
movwf acca+1 ; acca,acca+1 = freq / (2 ^ 16)
rrf acca, F
rrf acca+1, F ; acca+1 = freq / (2 ^ 17)
; For each entry in the band filter table, see if this frequency
; is within the limits.
movlw BANDS
movwf count
bandloop:
decf count, F
btfsc count, 7 ; Check for counter 'underflow'
goto noband ; We've run out of bands to check!
movf count, W
call lookupBandLow
subwf acca+1, W ; Compare with lower freq of band
btfss _C
goto bandloop ; If below bottom of this band, skip to next
movf count, W
call lookupBandHi
subwf acca+1, W ; Compare with upper freq of band
btfsc _Z
goto bandfound ; If equal to...
btfsc _C ; ...or less than, we have a match.
goto bandloop
; Here with count holding the filter to select, in the range
; 0 - BANDS. The filter selection latch is on the top 4 bits
; of PORTB, so just shift 'count' into the top nibble.
bandfound:
bcf _C
rlf count, F
rlf count, F
rlf count, F
rlf count, F
retlw 0
; Here if current frequency is outside any of the band pass
; filters - just select a general coverage low-pass filter, which
; is defined as the next one beyond the top band pass filter
noband:
movlw BANDS
movwf count
goto bandfound
;**********************************************************************
;
; DDSreset
; --------
;
; Reset both DDS chips under s/w control. If a band filter selection
; latch is fitted, the top line is wired to the DDS chip reset inputs.
; This routine briefly raises that line before resetting the latch
; to a 'safe' value.
;
; Input: none
;
; Output: none
;
; Postconditions: All PORTB lines are left configured as outputs.
; The default (i.e. 'null') band-pass filter is left
; selected on the basis that the DDS chips will need
; to be reprogrammed, and hence the correct band-pass
; filter will be re-selected anyway.
;
;**********************************************************************
DDSreset:
; Hardware reset of DDS chips does not clear internal 'input
; register', so we explicitly clear it first.
movlw .40
movwf tmp ; Loop counter - clear all 40 bits
res_loop:
bcf DDS_DATA_A
bcf DDS_DATA_B
bsf DDS_CLOCK ; Clock in next bit
nop
bcf DDS_CLOCK
decfsz tmp, F ; Repeat for remaining bits
goto res_loop
clrw
tris PORTB ; Prepare by making PORTB all outputs.
movlw 0F0
movwf PORTB ; Raise the 'top' line on the decoder/latch
bsf DDS_FQ_UD ; ...and latch in that value.
nop
bcf DDS_FQ_UD
movlw BANDS * 16 ; Now select the 'null' filter - an
movwf PORTB ; arbitrary choice just to ensure that
bsf DDS_FQ_UD ; the top line of the latch is deselected
nop
bcf DDS_FQ_UD
retlw 0
#endif
;***************************************************************
;
; Start of main processing loop
;
;***************************************************************
start:
clrwdt
clrf STATUS ; clear processor flags
clrw
option ; Enable 'weak pull-ups' on PORTB
movwf INTCON ; Ensure interrupts disabled for now
#ifndef MOUSE_STYLE
bsf GIE ; ..although enable global interrupts
#endif
tris PORTA ; Initially set all I/O lines to outputs
tris PORTB
movwf PORTA ; Initialise all outputs to 0
movwf PORTB
; Delay a while at power-up. This is really only important if
; no band filter switching provided, where the DDS chips must be
; reset at power-up by additional hardware:in that case, delay
; here for 500ms to ensure that the chips have fully reset. In
; practice it seems to improve power-up reliability in all cases
; so we perform the delay unconditionally.
movlw .100
movwf tmp
startdelay:
call wait5ms
decfsz tmp, f
goto startdelay
#ifdef BAND_SWITCH
; Reset both DDS chips to ensure that they are initially in-phase
call DDSreset
#endif
call DDSserMode ; Put DDS chips into 'serial' mode
call initDisplay ; Initialise LCD controller
call DispMode ; Display initial state of CW/USB/LSB selection
movlw COUNT_INIT
movwf speed ; Initialise 'tuning speed' monitor
; At start-up default to a sensible frequency - normally over-riden from
; memory store 0 anyway: this value is only used if the memory recall fails
; (e.g. nothing stored yet).
clrf daccc
movlw LOW (START_FREQ / 10000)
movwf daccc+1
movlw HIGH (START_FREQ % 10000)
movwf daccc+2
movlw LOW (START_FREQ % 10000)
movwf daccc+3
call setFrequency ; Program DDS chips to change to the new
; frequency. Also results in new frequency
; being copied back into 'freq', the band
; select latches being set, and an LCD update!
clrf lastkey ; If possible, start at the frequency
goto get_memory ; stored in memory 0
; Re-configure I/O lines for keyboard + optical encoder input
; and re-enable interrupts. This is probably only necessary if
; 'BAND_SWITCH' is defined, but can be needed under other
; circumstances as well. Rather than trying to work out if it
; is necessary, we just do it always.
reset_IO:
movlw KEY_IO_MASK
tris PORTB
#ifndef MOUSE_STYLE
bcf INTF
bsf INTE ; Enable RB0/INT interrupts so that
bcf opticlk ; we see optical encoder clock pulses
#endif
; Start of inner loop which is executed while we are waiting
; for a key to be pressed or the optical encoder to register
; some movement.
mainloop:
clrwdt ; Ensure watchdog is reset
; First check if it is time to perform a deferred update of the
; LCD display
movf LCDtimer, W
btfsc _Z
goto no_lcd_update ; Jump if no LCD update requested
decf LCDtimer, F
btfss _Z
goto no_lcd_update ; Not time to perform the update yet
#ifndef MOUSE_STYLE
bcf INTE ; Ensure RB0/INT interrupts are disabled
#endif
movlw LCD_HOME
call writeLCDinst ; Ensure the display cursor is 'home'
call DispFreq ; before we update the display (since
goto reset_IO ; DispFreq changes PORTB config.)
; Copy current frequency into daccc ready for adjustment.
no_lcd_update:
movlw freq
movwf FSR
movlw daccc
call dblcpy ; daccc <- freq
; Check for a key press first - takes priority over optical encoder
call readKey
btfsc _Z
goto nokey
; Here when a key is pressed (key number in tmp, 1 - 12)
movf tmp, W
xorwf lastkey, W ; Is this a different key from last time ?
btfss _Z
goto newkey ; Yes, so action it immediately
; Here if the same key is still held down. Check if it is time to
; auto-repeat
decf rptcount, F
btfsc _Z
goto keyproc
call wait1ms
goto mainloop ; Not time to auto-repeat, so keep waiting
; Here if a new/different key has been pressed.
newkey:
movf tmp, W
movwf lastkey ; Remember which key is now pressed
keyproc:
movlw RPT_INIT
movwf rptcount ; Reset auto-repeat counter for next time
; Process a key press via jump table. The simple mechanism used here
; relies on the whole of the jump table being in the bottom 256 words
; of program store - hence the compile-time check at the end!!!
movf lastkey, W
addwf PC, F
goto act_direct
goto act_up50
goto act_up5k
goto act_up500k
goto act_down50
goto act_down5k
goto act_down500k
goto act_lsbusb
goto act_store
goto act_recall
goto mainloop ; '*' key
goto mainloop ; Invalid
goto mainloop ; '#' key
#if ($ > .256)
echo "Jump table out of permissible memory area"
#endif
act_up50: ; ************* Increment by 50Hz *************
call set50Hz ; dacca <- 50
goto tune_up ; tune up by 50Hz
act_up5k: ; ************* Increment by 5KHz *************
call set5KHz ; dacca <- 5000
goto tune_up ; tune up by 5KHz
act_up500k: ; ************* Increment by 500KHz *************
call set500KHz ; dacca <- 500000
goto tune_up ; tune up by 500KHz
act_down50: ; ************* Decrement by 50Hz *************
call set50Hz ; dacca <- 50
goto tune_down ; tune down by 50Hz
act_down5k: ; ************* Decrement by 5KHz *************
call set5KHz ; dacca <- 5000
goto tune_down ; tune down by 5KHz
act_down500k: ; ************* Decrement by 500KHz *************
call set500KHz ; dacca <- 500000
goto tune_down ; tune down by 500KHz
act_lsbusb: ; ************* Change mode LSB/USB/CW **********
btfsc cwflag
goto setlsbmode ; Was CW mode, so change to LSB
btfsc usbflag
goto setcwmode ; Was USB, so change to CW
bsf usbflag ; Here if changing from LSB to USB
goto changemode
setcwmode:
bsf cwflag ; Here to change from USB to CW - leave
goto changemode ; USB flag set since CW is handled in a
; very similar manner to USB.
setlsbmode:
bcf cwflag ; Here to change to LSB mode - ensure that
bcf usbflag ; CW flag is cleared again.
changemode: ; (Can also come here from 'memory recall')
#ifndef MOUSE_STYLE
bcf INTE ; Ensure RB0/INT interrupts are disabled
#endif
call DispMode ; Update LCD display
#ifdef BAND_SWITCH
; If we have the ability to reset the DDS chips under s/w control
; then do so each time that we change sidebands. This should help
; to ensure that the phase relationship between them stays the same.
call DDSreset
call wait5ms
call DDSserMode ; Put DDS chips back into serial mode
#endif
goto dds_update
not_usblsb:
act_direct: ; ************* Direct freq entry *************
call directFreqEntry ; Get new frequency from user (into daccc)
goto dds_update ; Select entered (and validated) frequency
act_store: ; ************* Memory Store *************
movlw LCD_CLEAR
call writeLCDinst ; Clear display, home cursor
movlw 'S'
call writeLCDdata ; Display prompt on top row
movlw 'e'
call writeLCDdata
movlw 't'
call writeLCDdata
movlw ' '
call writeLCDdata
call memoryPrompt ; Ask which memory
btfss _Z ; Skip if invalid memory specified
call memoryStore ; If OK, store freq there
goto reset_IO ; No further action needed
act_recall: ; ************* Memory recall *************
movlw LCD_CLEAR
call writeLCDinst ; Clear display, home cursor
movlw 'G'
call writeLCDdata ; Display prompt on top row
movlw 'e'
call writeLCDdata
movlw 't'
call writeLCDdata
movlw ' '
call writeLCDdata
call memoryPrompt ; Ask which memory
btfss _Z ; Skip if invalid memory specified
get_memory:
call memoryRecall ; If OK, recall freq from there
call checkFrequency ; Check that the result looks sensible
btfsc _C
goto reset_IO ; Ignore operation if result not OK
movf tmp, W ; If all OK, update mode flags accordingly
movwf flags ; (WARNING: relies on other flags being
; irrelevant at this point!!!)
goto changemode ; Display new mode and select newly recalled
; frequency
; Here when no key is being pressed - update 'lastkey'
nokey:
movlw NO_KEY
movwf lastkey
; No keys being pressed, so check for movement of the optical encoder
clrw
#ifdef MOUSE_STYLE
movf OPT_PORT, W
andlw OPT_MASK
movwf tmp ; Save new input value in tmp for the moment
subwf last, W
btfsc _Z
goto nopulse ; No change from rotary encoder
bsf optidir ; Only get here on a transition OPT_1 or OPT_2
clrw ; ..determine direction by comparing value of
btfsc last, OPT_1 ; *previous* OPT_1 with *new* OPT_2 (thanks
movlw 0FF ; to G0BBL for this cunning algorithm)
xorwf tmp, F
btfss tmp, OPT_2
bcf optidir
xorwf tmp, W ; Restore new reading to W
movwf last ; New reading becomes next time's 'old value'
#else
btfss opticlk
goto nopulse ; No clock pulses, so just keep waiting
#endif
; Here if optical encoder has moved. Decrement 'opticlk' and
; load current tuning step size into dacca
#ifdef VARI_TUNE
#ifndef MOUSE_STYLE
bcf INTE ; Ensure interrupts are disabled before
bcf opticlk ; calling getstep because it can mess up
#endif ; the table lookup (or so I'm told!)
call getstep
#else
call set50Hz ; If using fixed-rate tuning, then
; always tune by 50Hz.
#endif
; Check for direction of movement, and add or subtract step size
btfss optidir
goto tune_down
tune_up:
call dbladd ; daccc <- freq + <step size>
goto change_freq
tune_down:
call dblsub ; daccc <- freq - <step size>
; Here if the user has requested a change to the frequency or mode.
; The new frequency is always in daccc at this point, and the
; mode flags are in 'flags'. Perform a quick range-check on the
; frequency before re-programming the DDS chips etc.
change_freq:
call checkFrequency
btfss _C
dds_update:
call setFrequency ; Program DDS chips to change to the new
; frequency. Also results in new frequency
; being copied back into 'freq', the band
; select latches being set, and an LCD update!
goto reset_IO
; Here if there have been no clock pulses from the optical
; encoder - decrement the tuning speed indicator.
nopulse:
movf speed, W
btfss _Z
decf speed, F
call wait1ms ; This delay should dominate the overall
; 'idle' cycle time, improving timing
; consistency.
goto mainloop
;**********************************************************************
;
; DDSserMode
; ----------
;
; Put both DDS chips into 'serial load' mode (we always program them
; serially here). The algorith for this is taken straight from the
; AD9850 data sheet.
;
; Input: none
;
; Output: none
;
; Preconditions: Assumes that the PIC pins used for DDS clock and
; update lines have already been configured as outputs.
;
;**********************************************************************
DDSserMode:
bsf DDS_FQ_UD ; Reset input registers of both DDS chips
nop
bcf DDS_FQ_UD
bsf DDS_CLOCK ; Clock in a single data bit
nop
bcf DDS_CLOCK
bsf DDS_FQ_UD ; Reset input registers again
nop
bcf DDS_FQ_UD
retlw 0
;**********************************************************************
;
; setFrequency
; ------------
;
; This routine is the core of the DDS control sotware.
;
; It prepares the DDS chips to be set to a new frequency and/or sideband
; setting. A 32-bit frequency value is clocked out to both DDS chips.
; DDS1 is then set to a relative phase shift of 0, whilst DDS2 is set
; to either +90 degrees or +270 degrees (i.e. -90 degrees) depending on
; which sideband is selected.
;
; NOTE: the frequency setting of the DDS chips may be offset slightly from
; the specified frequency, depending on the current mode (USB/LSB/CW).
; See below for details.
;
; As well as reprogramming the DDS chips, this routine optionally sets
; the band filter selection latches to the appropriate band for the
; specified frequency, AND requests a deferred update of the LCD display!
;
; Inputs: Frequency to tune to in daccc
; SSB/CW mode indicated by cw_flag
; Required sideband selection in usbflag
;
; Output: none
;
; Preconditions: Assumes that the PIC pins used for DDS clock and
; data lines have already been configured as outputs.
;
; Postconditions: freq is set equal to the specified frequency
; Numerous other registers are corrupted!
;
;**********************************************************************
setFrequency:
movlw daccc
movwf FSR
movlw freq
call dblcpy ; Make sure freq reflects new frequency
; Optionally offset frequency which we set DDS chips to by +/- 1.5 KHz
; (depending on sideband), so that a specified frequency results
; in exactly that frequency being heard. In CW mode offset by
; 800Hz since this seems to a typical preferred CW pitch.
clrf dacca
clrf dacca+1
btfss cwflag
goto not_cwmode
movlw HIGH CWTONE_OFFSET
movwf dacca+2
movlw LOW CWTONE_OFFSET
movwf dacca+3 ; dacca <- required CW tuning offset
#ifdef SSB_OFFSET
goto adj_freq
not_cwmode:
movlw HIGH SIDEBAND_OFFSET
movwf dacca+2
movlw LOW SIDEBAND_OFFSET
movwf dacca+3 ; dacca <- required sideband offset
adj_freq:
btfss usbflag
goto lower_sb
call dblsub ; For USB, need to tune below required sideband
goto set_freq
lower_sb:
call dbladd ; For LSB, need to tune above required sideband
#else ; No optional SSB offset
call dblsub ; Always treat CW as USB
not_cwmode:
#endif
set_freq:
; Here with required tuning frequency in daccc.
; Calculate DDS programming value from the frequency, by performing
; (<frequency> * 2^32) / <clock>. First move daccc * 2^32 into
; daccd + dacce
movlw daccc
movwf FSR
movlw daccd
call dblcpy ; daccd <- required frequency
clrf dacce
clrf dacce + 1
clrf dacce + 2
clrf dacce + 3
; Try to read reference clock rate from EEPROM, so that it can be tuned
; to compensate for crystal errors.
movlw REF_XTAL_LOC * 4
movwf EEADR
movlw dacca ; Read ref. frequency into dacca
movwf FSR
call eeread ; Read 32-bit value from EEPROM
movlw HIGH (DDS_CLOCK_SPEED / 10000)
xorwf dacca, W ; Check for a sensible value
btfsc _Z
goto got_ref
; If EEPROM value looks unreasonable, default to expected value
; (only approximate to save memory)
movlw HIGH (DDS_CLOCK_SPEED / 10000)
movwf dacca
movlw LOW (DDS_CLOCK_SPEED / 10000)
movwf dacca+1
movlw HIGH (DDS_CLOCK_SPEED % 10000)
movwf dacca+2
clrf dacca+3 ; (Lowest bits insignificant)
got_ref:
call dbldiv ; DDS programming value in daccb
; Clock out resulting 'increment' value to DDS chips, together
; with their relative phase information.
bcf DDS_CLOCK
movlw .32
movwf tmp ; Loop counter for 32-bit value
dds_loop1:
bcf DDS_DATA_A
bcf DDS_DATA_B
rrf daccb, F
rrf daccb+1, F
rrf daccb+2, F
rrf daccb+3, F ; Shift each bit into carry, LSB first
btfss _C
goto dds_low1
bsf DDS_DATA_A ; Program DDS-a and -b identically
bsf DDS_DATA_B
dds_low1:
bsf DDS_CLOCK ; Clock in next data bit
nop
bcf DDS_CLOCK
decfsz tmp, F ; Repeat for remain 'increment' bits
goto dds_loop1
; ...then clock out 'phase' and 'control' bits for DDS-a and
; -b together.
movlw .8
movwf tmp ; Loop counter for 8-bit value
movlw 00 ; 0 deg. phase shift DDS1
movwf acca
movlw 40 ; +90 deg. phase shift for DDS2
btfsc usbflag
movlw 0C0 ; Depending on sideband, use +270 deg for DDS2
movwf acca+1
dds_loop2:
bcf DDS_DATA_A
bcf DDS_DATA_B
rrf acca, F
btfss _C
goto dds_low2
bsf DDS_DATA_A ; Program DDS-a
dds_low2:
rrf acca+1, F
btfss _C
goto dds_low3
bsf DDS_DATA_B ; Program DDS-b
dds_low3:
bsf DDS_CLOCK ; Clock in next phase bit
nop
bcf DDS_CLOCK
decfsz tmp, F ; Repeat for remaining phase bits
goto dds_loop2
; If pre-mixer band-pass filters are provided, the filter selection
; latches are also triggered by the DDS FQ_UD line, so we must
; ensure that the correct latch output value is already set on
; PORTB before toggling FQ_UD.
#ifdef BAND_SWITCH
#ifndef MOUSE_STYLE
bcf INTE ; Ensure RB0/INT interrupts are disabled
#endif
clrw ; before making PORTB lines outputs
tris PORTB ; (see file header for explanation)
call getlatch
movf count, W
movwf PORTB
#endif
; Trigger the update by raising FQ_UD line
bsf DDS_FQ_UD
nop
bcf DDS_FQ_UD
; Now that the DDS chips have been re-programmed, request a
; deferred update of the LCD display (if one hasn't been requested
; already) to ensure that it reflects the new frequency.
movf LCDtimer, W
btfsc _Z
movlw LCD_UPDATE_INTERVAL
movwf LCDtimer
retlw 0
;**********************************************************************
;
; checkFrequency
; --------------
;
; This routine performs a crude range check on a specified frequency.
;
; Inputs: Frequency to check is in daccc
;
; Output: Carry flag is set is frequency is out of permissible range
;
; Postconditions: no registers corrupted
;
;**********************************************************************
checkFrequency:
movlw (MAX_FREQ + 0FFFFFF) / 1000000
subwf daccc, W
btfsc _C
retlw 0 ; New freq is negative or top byte is too big
movf daccc, W
iorwf daccc+1, W
btfsc _Z ; Also disallow frequencies < 64K
bsf _C
retlw 0
;**********************************************************************
;
; wait1ms
;
; Routine to delay for about 1ms - NOT to be used for critical timing
; without further tuning.
;
; Postconditions: registers 'delayhi' and 'delaylo' are corrupted!
;
; Inputs: none
;
; Outputs: none
;**********************************************************************
wait1ms:
movlw .10
movwf delayhi
w1outer:
movlw .33
movwf delaylo
w1inner:
decfsz delaylo, F
goto w1inner ;Inner loop delays for about 100us
clrwdt ; MUST reset h/w watchdog or it will trip.
decfsz delayhi, F
goto w1outer ;Outer loop delays for 10 * 100us = 1ms
retlw 0
;**********************************************************************
;
; wait5ms
;
; Routine to delay for about 5ms - NOT to be used for critical timing
; without further tuning.
;
; Postconditions: registers 'count', 'delayhi' and 'delaylo' are corrupted!
;
; Inputs: none
;
; Outputs: none
;**********************************************************************
wait5ms:
movlw .5
movwf count
w5loop:
call wait1ms
decfsz count, F
goto w5loop
retlw 0
;**********************************************************************
;
; writeLCDinst
; ------------
;
; Low level routine to write to the instruction register of
; the LCD controller. Waits a fixed period for the instruction to
; complete since we don't have enough I/O lines to check for a busy
; status from the display.
;
; Preconditions
; LCD latches have been initialised to reflect their inactive state
;
; Postconditions
; Byte is written to the instruction register
; tmp register is corrupted
; PORTB all lines are configured as outputs
;
; Parameters
; Byte to write in reg. W
;
; Returns
; Void
;**********************************************************************
writeLCDinst:
movwf tmp ; Save value to write
clrw ; Ensure LCD data lines are configured as outputs
tris PORTB
bcf LCD_ID
movf tmp, W
movwf LCD ; Output data to LCD controller
bsf LCD_EN ; Enable LCD controller
goto $+1
bcf LCD_EN ; ..and disable it again
goto wait5ms ; Delay long enough for worst-case
; instruction (clear screen) to finish
; (goto here is equivalent to call ...
; followed by retlw, but shorter!)
;**********************************************************************
;
; writeLCDdata
; ------------
;
; Routine to write a single character to the LCD display at the current
; cursor position. Waits a fixed period for the operation to
; complete since we don't have enough I/O lines to check for a busy
; status from the display.
;
; Preconditions
; writeLCDinst has been called to setup the location at
; which to print the character.
;
; Postconditions
; Outputs character to the LCD
; tmp register is corrupted
; PORTB all lines are configured as outputs
;
; Parameters
; Character code in reg. W
;
; Returns
; Void
;**********************************************************************
writeLCDdata:
movwf tmp ; Save value to write
clrw ; Ensure LCD data lines are configured as outputs
tris PORTB
bsf LCD_ID
movf tmp, W
movwf LCD ; Output data to LCD controller
bsf LCD_EN ; Enable LCD controller
goto $+1
bcf LCD_EN ; ..and disable it again
movlw .40
movwf delaylo
writedelay:
decfsz delaylo, F
goto writedelay ; Wait 120us for data write to complete
retlw 0
;**********************************************************************
;
; initDisplay
; -----------
;
; Initialises the LCD. Performs a software reset, ready for programming.
; (Algorithm taken from Hitachi LM016 data sheet)
;
; Preconditions
; None
;
; Postconditions
; LCD is initialised
;
; Parameters
; Void
;
; Returns
; Void
;**********************************************************************
initDisplay:
call wait5ms ; Wait 15ms
call wait5ms
call wait5ms
movlw LCD_8BIT
bcf LCD_ID
movwf LCD ; Output data to LCD controller
bsf LCD_EN ; Enable LCD controller
goto $+1
bcf LCD_EN
call wait5ms ; Wait 5ms
movlw LCD_8BIT
bcf LCD_ID
movwf LCD ; Output data to LCD controller
bsf LCD_EN ; Enable LCD controller
goto $+1
bcf LCD_EN
call wait1ms ; Wait > 100us (actually much more!)
movlw LCD_8BIT
bcf LCD_ID
movwf LCD ; Output data to LCD controller
bsf LCD_EN ; Enable LCD controller
goto $+1
bcf LCD_EN
goto $+1
; Now the device is reset, we can start to program it
movlw LCD_8BIT
call writeLCDinst
movlw LCD_DISABLE
call writeLCDinst ; LCD manual says we must disable and
movlw LCD_ENABLE ; re-enable the display !!! BUT !!!
call writeLCDinst ; their method is not self-consistent.
movlw LCD_MODESET
call writeLCDinst
movlw LCD_CLEAR
goto writeLCDinst ; Equivalent to call ... followed by
; retlw, but shorter!
;**********************************************************************
;
; scanrow
; -------
;
; Checks a single row of the keypad for a key press. Assumes that the
; keyboard I/O lines have been configured as inputs and outputs as
; appropriate, and the relevant row output has already been selected.
;
; Inputs: none
;
; Output: If key pressed, Z flag is clear, and W = column of first
; depressed key (1 - 3). Else Z flag is set and W = 0.
;
;**********************************************************************
scanrow:
; Check each column input in turn
bcf _Z
btfss KEY_IN1 ; Note: key inputs are active low
retlw 1
btfss KEY_IN2
retlw 2
btfss KEY_IN3
retlw 3
bsf _Z
retlw 0
;**********************************************************************
;
; readKey
; -------
;
; Checks the keypad for a key press. Assumes that the keyboard I/O
; lines have been configured as inputs and outputs as appropriate.
; NOTE: this routine doesn't block - it returns immediately, regardless
; of whether a key has been pressed.
;
; Inputs: none
;
; Output: If key pressed, Z flag is clear, and tmp = key value (0 - 9,
; HASH_KEY or STAR_KEY)
; Else Z flag is set and tmp is undefined.
;
;**********************************************************************
readKey:
; Try to reduce RFI by normally only checking if *any* key has
; been pressed, before bothering to scan for which one.
clrf tmp ; Initially assume no key pressed
bcf KEY_OUT1 ; Activate all keypad outputs
bcf KEY_OUT2 ; (Note: keyboard I/O is active low)
bcf KEY_OUT3
bcf KEY_OUT4
goto $+1
movlw ~KEY_IN_MASK ; ..and look for active inputs
iorwf PORTB, w ; (i.e. any which are *low*)
xorlw 0FF
btfsc _Z
retlw 0 ; No keys pressed, so don't bother
; scanning for which one
; Here when at least one key is being pressed - check which one.
bcf KEY_OUT1
bsf KEY_OUT2
bsf KEY_OUT3
bsf KEY_OUT4
movlw 0
movwf tmp ; Prepare to scan keys 1 - 3
call scanrow
btfss _Z
goto gotkey
bsf KEY_OUT1
bcf KEY_OUT2
movlw .3
movwf tmp ; Scan keys 4 - 6
call scanrow
btfss _Z
goto gotkey
bsf KEY_OUT2
bcf KEY_OUT3
movlw .6
movwf tmp ; Scan keys 7 - 9
call scanrow
btfss _Z
goto gotkey
bsf KEY_OUT3
bcf KEY_OUT4
movlw .9
movwf tmp ; Scan keys 10 - 12
call scanrow
btfsc _Z
retlw 0
; Here when a key has been pressed. (W + tmp) gives the key number
; in the range 1 to 12. However, we need to watch out for (and
; adjust) a value of ZERO_KEY which has a key code that differs
; from its numeric value
gotkey:
addwf tmp, F
movlw ZERO_KEY
xorwf tmp, W
btfsc _Z
clrf tmp ; Convert code of ZERO_KEY to numeric 0
bcf _Z
retlw 1
;**********************************************************************
;
; setup
;
; This routine is used by the multiply and divide routines to put their
; parameters into the correct form. It is taken (almost) directly from AN512.
; accb is moved to accd (and accb cleared), and accc is copied to acce.
;
; Inputs: see above
;
; Output: see above
;
;**********************************************************************
setup: MOVF accb,W ;MOVE B TO D
MOVWF accd
MOVF accb+1,W
MOVWF accd+1
MOVF accc,W
MOVWF acce
MOVF accc+1,W
MOVWF acce+1
CLRF accb
CLRF accb+1
RETLW 0
;**********************************************************************
;
; mpy (and local routine madd)
;
; This code performs a 16-bit multiply. It is taken directly from
; AN512, so little is known about it!
;
; Inputs: 16 bit values in acca and accb
;
; Output: 32 bit result left in accb and accc
;
;**********************************************************************
madd MOVF acca+1,W
ADDWF accb+1, F ;ADD LSB
BTFSC 3,0 ;ADD IN CARRY
INCF accb, F
MOVF acca,W
ADDWF accb, F ;ADD MSB
RETLW 0
mpy CALL setup ;RESULTS IN B(16 MSB'S) AND C(16 LSB'S)
MOVLW 10
MOVWF tmp
mloop RRF accd, F ;ROTATE D RIGHT
RRF accd+1, F
SKPNC ;NEED TO ADD?
CALL madd
RRF accb, F
RRF accb+1, F
RRF accc, F
RRF accc+1, F
DECFSZ tmp, F ;LOOP UNTIL ALL BITS CHECKED
GOTO mloop
RETLW 0
;**********************************************************************
;
; div
;
; This code performs a 16-bit divide. It is taken directly from
; AN512, so little is known about it!
;
; WARNING if divisor is >15 bits, internal overflow can occur, resulting
; in incorrect output for *some* input values.
;
; Inputs: 32 bit value in accb and accc, 15 bit divisor in acca.
;
; Output: 16 bit result in accb, 16 bit remainder in accc.
;
; Notes: Uses one level of stack, so can only be called from the top-level
; code.
;
;**********************************************************************
div: CALL setup
MOVLW 20
MOVWF tmp
CLRF accc
CLRF accc+1
dloop: CLRC
RLF acce+1, F
RLF acce, f
RLF accd+1, F
RLF accd, F
RLF accc+1, F
RLF accc, F
MOVF acca,W
SUBWF accc,W ;CHECK IF A>C
SKPZ
GOTO nochk
MOVF acca+1,W
SUBWF accc+1,W ;IF MSB EQUAL THEN CHECK LSB
nochk: SKPC ;CARRY SET IF C>A
GOTO nogo
MOVF acca+1,W ;C-A INTO C
SUBWF accc+1, F
BTFSS 3,0
DECF accc, F
MOVF acca,W
SUBWF accc, F
SETC ;SHIFT A 1 INTO B (RESULT)
nogo: RLF accb+1, F
RLF accb, F
DECFSZ tmp, F ;LOOP UNTILL ALL BITS CHECKED
GOTO dloop
RETLW 0
;**********************************************************************
;
; dblcpy
; ------
;
; Copies a 4-byte (aka double length) variable into another 4-byte
; variable.
;
; Inputs: FSR already points to source variable, W points to
; destination variable
;
; Output: none
;
; Postconditions: corrupts count and tmp.
;
;**********************************************************************
dblcpy:
movwf tmp ; Save dest pointer in tmp
movlw 4
movwf count
cpyloop:
movf INDF, W ; Get next byte
movwf EEDATA ; ..and save it
incf FSR, W
movwf EEADR ; Save incremented source addr.
movf tmp, W
movwf FSR ; Set up destination addr.
movf EEDATA, W
movwf INDF ; Copy byte to destination
incf FSR, W
movwf tmp ; Save incremented dest addr.
movf EEADR, W
movwf FSR ; Restore source addr to FSR
decfsz count, F
goto cpyloop ; Repeat for other bytes
retlw 0
;**********************************************************************
;
; dblcomp
; -------
;
; Double length compare routine - compares a 32-bit number in dacca
; with the 32-bit number in daccc. Used by the double-length divide
; routine.
;
; Inputs: 32 bit values in dacca and daccc
;
; Output: Carry is clear if A > C, carry is set A <= C
;
;**********************************************************************
dblcomp:
MOVF dacca,W
SUBWF daccc,W ;Check MSB first
SKPZ
RETLW 0
MOVF dacca+1,W
SUBWF daccc+1,W ;If MSB equal then check less significant bytes
SKPZ
RETLW 0
MOVF dacca+2,W
SUBWF daccc+2,W
SKPZ
RETLW 0
MOVF dacca+3,W
SUBWF daccc+3,W
RETLW 0
;**********************************************************************
;
; dbladd
; ------
;
; Double length addition routine - adds a 32-bit number in dacca
; to the 32-bit number in daccc.
;
; Inputs: 32 bit values in dacca and daccc
;
; Output: daccc <- daccc + dacca
;
;**********************************************************************
dbladd:
movf dacca+3, W
addwf daccc+3, F ;add LSB
btfss _C
goto nocarry1
incf daccc+2, F ;add in carry
btfss _Z
goto nocarry1
incf daccc+1, F ;..and propagate on if necessary
btfsc _Z
incf daccc, F
nocarry1:
movf dacca+2, W
addwf daccc+2, F ;...repeat for other bytes
btfss _C
goto nocarry2
incf daccc+1, F
btfsc _Z
incf daccc, F
nocarry2:
movf dacca+1, W
addwf daccc+1, F
btfsc _C
incf daccc, F
movf dacca, W
addwf daccc, F
retlw 0
;**********************************************************************
;
; dblsub
; ------
;
; Double length subtract routine - subtracts a 32-bit number in dacca
; from the 32-bit number in daccc. Only tested for the case where
; daccc > dacca.
;
; Inputs: 32 bit values in dacca and daccc
;
; Output: daccc <- daccc - dacca
;
;**********************************************************************
dblsub:
movf dacca+3, W
subwf daccc+3, F ;subtract LSB
btfsc _C
goto noborrow1
decf daccc+2, F ;subtract borrow
incf daccc+2, W
btfss _Z
goto noborrow1
decf daccc+1, F ;..and propagate on if necessary
incf daccc+1, W
btfsc _Z
decf daccc, F
noborrow1:
movf dacca+2, W
subwf daccc+2, F ;...repeat for other bytes
btfsc _C
goto noborrow2
decf daccc+1, F
incf daccc+1, W
btfsc _Z
decf daccc, F
noborrow2:
movf dacca+1, W
subwf daccc+1, F
btfss _C
decf daccc, F
movf dacca, W
subwf daccc, F
retlw 0
;**********************************************************************
;
; dbldiv
; ------
;
; Double length divide routine - divides a 64-bit number by a
; 32-bit number, to give a 32-bit result and a 32-bit remainder.
; Loosely based on a 16-bit divide routine in AN512.
;
; WARNING if divisor is >31 bits, internal overflow can occur, resulting
; in incorrect output for *some* input values.
;
; Inputs: 64 bit value in daccd and dacce, 31 bit divisor in dacca.
;
; Output: 32 bit result in daccb, 32 bit remainder in daccc.
;
;**********************************************************************
dbldiv:
movlw 40
movwf tmp ; Number of bits in dividend
clrf daccb ; Clear 32-bit accumulator B
clrf daccb+1
clrf daccb+2
clrf daccb+3
clrf daccc ; Clear 32-bit accumulator C
clrf daccc+1
clrf daccc+2
clrf daccc+3
dbldloop:
clrc
rlf dacce+3, F ; Shift 64-bit dividend left one bit
rlf dacce+2, F
rlf dacce+1, F
rlf dacce, F
rlf daccd+3, F
rlf daccd+2, F
rlf daccd+1, F
rlf daccd, F
rlf daccc+3, F ; Shift MSB into bottom of 32-bit acc. C
rlf daccc+2, F
rlf daccc+1, F
rlf daccc, F
call dblcomp ; Check if A > C
skpc
goto dblnogo
call dblsub ; C <- (C-A)
setc ; Shift 1 into B (result)
dblnogo:
rlf daccb+3, F
rlf daccb+2, F
rlf daccb+1, F
rlf daccb, F
decfsz tmp, F
goto dbldloop ; Loop until all bits checked
retlw 0
;**********************************************************************
; getdigit
;
; This routine divides the 16 bit value in accc by 10, putting the
; result back into accc, and returning the remainder (i.e. the least
; significant decimal digit) into the register pointed to by FSR.
; NOTE: The FSR register is auto-incremented by this routine.
;
; Corrupts acca and accb.
;**********************************************************************
getdigit:
clrf accb
clrf accb+1
clrf acca
movlw .10
movwf acca+1
call div ; Least sig. digit in accc+1
movf accc+1, W
movwf INDF ; Return result directly to specified reg.
incf FSR, F ; (and prepare for next digit)
movf accb, W ; Put result back in accc
movwf accc
movf accb+1, W
movwf accc+1
retlw 0
;**********************************************************************
;
; DispFreq
; --------
;
; Displays the 32-bit frequency value in 'freq' on the LCD display.
; Assumes that frequency is no more than 8 digits ( i.e. < 100MHz)
; which is always true for this application
;
; Preconditions: Assumes that the display cursor is already in the
; required position
;
; Inputs: 32 bit value in freq
;
; Output: direct to LCD display
;
; Postconditions: leaves PORTB lines configured as all outputs
;
;**********************************************************************
DispFreq:
; Split up 32bit frequency into to two 16-bit quantities to get
; more manageable values to work with. Since we never need to
; handle values > 8 digits, we just divide by 10000 to get the top 4
; digits in one word and the bottom 4 digits in the remainder.
movlw HIGH .10000
movwf acca ; Put 10000 (decimal) in divisor (i.e. acca)
movlw LOW .10000
movwf acca+1
movlw freq
movwf FSR
movlw accb
call dblcpy ; accb/accc <- freq
call div ; Get top 4 digits worth in accb, bottom 4
; digits worth in accc.
movf accb, W
movwf delayhi ; Temporarily store MSDs in delayhi + delaylo
movf accb+1, W
movwf delaylo
; Extract least significant 4 digits
movlw digit1
movwf FSR
call getdigit ; Get least sig. digit
call getdigit ; ..and the next
call getdigit ; ..and the next
call getdigit ; ..and the next
;
; Recover most significant 4 digits back into accc
;
movf delayhi, W
movwf accc
movf delaylo, W
movwf accc+1
call getdigit ; Extract the fifth digit
call getdigit ; ..and the next
call getdigit ; ..and the next
call getdigit ; ..and the next
;
; Count how many significant digits there are in the result
;
movlw 8
movwf count ; Assume all eight digits are significant
movlw digit8
movwf FSR
cloop:
movf INDF, W ; Look at next digit
btfss _Z
goto disploop ; Found a non-zero digit
decf count, F
btfsc _Z
goto no_signal ; Skip if no more digits to look at
decf FSR, F
goto cloop
;
; Here if frequency = 0 Hz. Just display as '0'
;
no_signal:
movlw 1
movwf count
;
; Display the result - 'count' holds no. of significant digits
; FSR points to most significant digit
;
disploop:
movf INDF, W ; Look at next digit
iorlw 30 ; Convert binary to ASCII
call writeLCDdata ; ..and display it.
decf FSR, F
decf count, F
btfsc _Z
goto ShowAsHz ; All digits shown, must be showing Hz
movf count, W
xorlw 6 ; Check for 6 digits left to go -
btfsc _Z ; i.e. displaying result in MHz
goto ShowAsMHz
movf count, W
xorlw 3 ; Check for 3 digits left to go -
btfsc _Z ; i.e. displaying result in KHz
goto ShowAsKHz
goto disploop
;
; We get here if just showing Hz - i.e. f < 1KHz. Just add trailing
; spaces to ensure we don't 'leave behind' any of the previous value
;
ShowAsHz:
movlw 8
goto addspaces
;
; We get here if displaying result in KHz - i.e. 1KHz <= f < 1Mhz
; or in MHz - i.e. f >= 1Mhz. Display digits after decimal point.
; FSR points to next digit to be displayed, and 'count' contains
; number of digits left to display
;
ShowAsKHz:
ShowAsMHz:
movlw '.'
call writeLCDdata ; Display decimal point
sedloop:
movf INDF, W ; Look at next digit
iorlw 30 ; Convert binary to ASCII
call writeLCDdata ; ..and display it.
decf FSR, F
decf count, F
btfss _Z
goto sedloop
movlw 4
;
; Add trailing spaces to ensure we don't 'leave behind' any
; of the previous value - W holds no. of spaces needed.
;
addspaces:
movwf count
spaceloop:
movlw ' '
call writeLCDdata
decf count, F
btfss _Z
goto spaceloop
retlw 0
;**********************************************************************
;
; DispMode
; --------
;
; Displays the current mode/sideband selection, based on the state of
; the usbflag and cwflag bits.
;
; Inputs: cwflag indicates if in CW mode, otherwise usbflag gives current
; USB/LSB selection
;
; Output: direct to LCD display
;
; Postconditions: leaves PORTB lines configured as all outputs
;
;**********************************************************************
DispMode:
movlw LCD_SETADDR + 40
call writeLCDinst
btfss cwflag
goto ssbmode
movlw 'C'
call writeLCDdata
movlw 'W'
call writeLCDdata
movlw ' '
goto dispmodeend
ssbmode:
movlw 'U'
btfss usbflag
movlw 'L'
call writeLCDdata
movlw 'S'
call writeLCDdata
movlw 'B'
dispmodeend:
goto writeLCDdata ; Equivalent to call ... followed by
; retlw, but shorter!
;**********************************************************************
;
; directFreqEntry
; ---------------
;
; Performs all processing associated with direct frequency entry via
; the keypad. Displays the prompt and gets key presses (up to 5 or
; until a non-numeric key is pressed) to accumulate into the required
; frequency in KHz.
;
; Only returns when it has a sensible frequency to pass back (i.e. one
; which passes the range check performed by checkFrequency).
;
; Inputs: none
;
; Output: new 32-bit frequency in daccc
;
; Postconditions: Restores LCD display to its normal appearance EXCEPT
; for the frequency, which will be drawn later by the main loop anyway.
; Leaves PORTB lines configured as all outputs.
; Does not reprogram the DDS chips - this is the responsibility
; of the calling function.
; Corrupts value in freq.
;
;**********************************************************************
directFreqEntry:
movlw LCD_CLEAR
call writeLCDinst ; Clear display, home cursor
movlw 'K'
call writeLCDdata ; Display prompt on top row
movlw 'H'
call writeLCDdata
movlw 'z'
call writeLCDdata
movlw ':'
call writeLCDdata
movlw LCD_SETADDR + .10
call writeLCDinst
movlw '#'
call writeLCDdata
movlw ' '
call writeLCDdata
movlw 'e'
call writeLCDdata
movlw 'n'
call writeLCDdata
movlw 'd'
call writeLCDdata
movlw 's'
call writeLCDdata
entry_redo:
clrf freq
clrf freq+1 ; Initially clear frequency to 0
clrf freq+2
clrf freq+3
entry_lp1:
movlw LCD_SETADDR + 40
call writeLCDinst ; Move cursor to 2nd row
call DispFreq ; Show new freq (in KHz)
movlw KEY_IO_MASK ; Reset keyboard I/O lines to correct state
tris PORTB
entry_lp2:
call wait1ms ; (should help reduce keybounce!)
call readKey
btfss _Z
goto keydown
movlw NO_KEY
movwf lastkey
goto entry_lp2 ; Wait until next key press
keydown:
movf tmp, W
xorwf lastkey, W ; Is this a different key from last time ?
btfsc _Z
goto entry_lp2 ; No, so ignore it (no auto-repeat here!)
; Here if a new/different key has been pressed
movf tmp, W
movwf lastkey ; Remember which key is now pressed
xorlw HASH_KEY
btfsc _Z
goto entry_end ; Hash key pressed - exit direct entry mode
movf tmp, W
xorlw STAR_KEY
btfsc _Z
goto entry_end ; Star key pressed - exit direct entry mode
; Here if a numeric key was pressed. lastkey holds numeric value
; of key which has been pressed. Perform freq = (freq * 10) + lastkey
; to get new frequency (most maths can be 16 bit because max valid
; value is 30,000 KHz).
movf freq+3, W
movwf acca+1 ; Save LSB
movf freq+2, W
movwf acca ; Save MSB
movlw .10
movwf accb+1
clrf accb
call mpy ; accb/accc holds 32-bit result
; Perform a rough range check on the intermediate value
movf accb+1, W
btfss _Z
goto entry_lp2 ; If > 16bit result, then ignore last digit
movlw (HIGH (MAX_FREQ / .1000))+1
subwf accc, W ; Check MSB of 16-bit result
btfsc _C
goto entry_lp2 ; Result too big, ignore last digit
; Add in new digit (don't worry about overflow above 16 bits, since
; we've just range checked it).
movf lastkey, W
addwf accc+1, F
btfsc _C
incf accc, F
; Here if last digit was legal, so put result back into freq.
; (Could do a more careful range check here - the current code allows
; frequencies up to about 30200 KHz to be entered. Since this is harmless
; it doesn't seem worth the extra complexity to stop it.)
movf accc+1, W
movwf freq+3 ; Ignore top 16 bits, and put rest back
movf accc, W ; back into freq.
movwf freq+2
goto entry_lp1
; Here with new frequency in KHz in freq. Convert to Hz ready
; for return to main processing loop
entry_end:
movf freq+3, W
movwf acca+1 ; Save LSB
movf freq+2, W
movwf acca ; Save MSB
movlw LOW .1000
movwf accb+1
movlw HIGH .1000
movwf accb
call mpy ; accb/accc hold 32-bit result
movlw accb
movwf FSR
movlw daccc
call dblcpy ; Put result into daccc
; Final range check before returning, since returning an invalid
; frequency can cause chaos!
call checkFrequency
btfsc _C
goto entry_redo
; Restore normal display before returning
movlw LCD_CLEAR
call writeLCDinst ; Clear display, home cursor
goto DispMode ; Equivalent to 'call DispMode, return'
;**********************************************************************
;
; memoryPrompt
; ------------
;
; Displays "memory: " on the LCD display and waits for the user to
; press a key 0 - 9. The resulting key press is returned in lastkey. If
; the user presses a non-numeric key the Z flag is set to indicate
; an error (otherwise cleared).
;
; Inputs: none
;
; Output: lastkey contains the selected memory number 0 - 9.
; Z flag is cleared if OK, set on an error.
;
; Preconditions: LCD display is assumed to be ready to start writing
; the prompt - i.e. the cursor is positioned as required etc.
;
; Postconditions: Restores LCD display to its normal appearance, but
; leaves PORTB lines configured as all outputs.
; Does not read or update the memory - this is the responsibility
; of the calling function.
;
;**********************************************************************
memoryPrompt:
movlw 'm'
call writeLCDdata ; Display prompt on top row
movlw 'e'
call writeLCDdata
movlw 'm'
call writeLCDdata
movlw ':'
call writeLCDdata
movlw KEY_IO_MASK ; Reset keyboard I/O lines to correct state
tris PORTB
mem_lp2:
call wait1ms ; (should help reduce keybounce!)
call readKey
btfss _Z
goto mem_key
movlw NO_KEY
movwf lastkey
goto mem_lp2 ; Wait until next key press
mem_key:
movf tmp, W
xorwf lastkey, W ; Is this a different key from last time ?
btfsc _Z
goto mem_lp2 ; No, so ignore it (no auto-repeat here!)
; Here if a new/different key has been pressed
movf tmp, W
movwf lastkey ; Remember which key is now pressed
; Restore normal display
movlw LCD_CLEAR
call writeLCDinst ; Clear display, home cursor
call DispFreq
call DispMode
; Now check which key was pressed
movf lastkey, W
xorlw HASH_KEY
btfsc _Z
retlw 0 ; Hash key pressed - exit memory prompt mode
movf lastkey, W
xorlw STAR_KEY
btfsc _Z
retlw 0 ; Star key pressed - exit memory prompt mode
; Here if a numeric key was pressed.
movf lastkey, W
movwf tmp ; Return memory number in tmp
bcf _Z ; Clear Z flag to show all OK
retlw 0
;**********************************************************************
;
; memoryStore
; -----------
;
; Stores the current value of 'freq' in a specified non-volatile
; memory area. Also stores the current mode (USB/LSB/CW).
;
; Inputs: the required memory area is in 'lastkey', range 0 - 9.
;
; Output: none
;
; Postconditions: lastkey is preserved
;
;**********************************************************************
memoryStore:
bcf _C
rlf lastkey, W ; W <- memory area * 2
movwf EEADR
rlf EEADR, F ; EEADR <- memory area * 4
; ...i.e. start address of relevant memory
movlw 4
movwf count ; Prepare to write 4 bytes
movlw freq ; ...of frequency value
movwf FSR
movf flags, W ; Get mode (LSB/USB/CW)
andlw MODE_MASK ; (and ignore other flags)
iorwf freq, F ; Save in top few bits of frequency value
#ifndef MOUSE_STYLE
bcf INTE ; Ensure RB0/INT interrupts are disabled
#endif
storloop:
movf INDF, W ; Get next byte of freq
movwf EEDATA ; ...ready to store it
bsf RP0 ; Select reg. bank 1
bsf WREN ; Follow 'magic' sequence to enable
movlw 055h ; write access to EEPROM memory
movwf EECON2 ; (see PIC16C84 datasheet for details)
movlw 0AAh
movwf EECON2
bsf WR
storwait:
clrwdt
btfsc WR ; Wait until EEPROM write has completed
goto storwait
bcf RP0 ; Select reg. bank 0 again
incf FSR, F ; Repeat for remaining bytes
incf EEADR, F
decf count, F
btfss _Z
goto storloop
bcf WREN ; Finished writing, so disable write access
#ifndef MOUSE_STYLE
bsf INTE ; Safe to re-enable RB0/INT interrupts again
#endif
movlw ~MODE_MASK
andwf freq, F ; Remove mode flags from 'freq' again
retlw 0
;**********************************************************************
;
; eeread
; ------
;
; Low level routine to read a 32-bit value from EEPROM.
;
; Inputs: EEADR already set to the first EEPROM location to read
; FSR pointing to the first byte of the destination buffer
;
; Output: 32-bit location pointed to by FSR contains the value read
;
; Postconditions: corrupts 'count', EEADR and FSR
;
;**********************************************************************
eeread:
movlw 4
movwf count ; Prepare to read 4 bytes
recaloop:
bsf RP0 ; Select reg. bank 1
bsf RD ; Request read from EEPROM
bcf RP0 ; Re-select bank 0
movf EEDATA, W ; Read next byte
movwf INDF ; ..and put it into freq.
incf FSR, F ; Repeat for remaining bytes
incf EEADR, F
decf count, F
btfss _Z
goto recaloop
retlw 0
;**********************************************************************
;
; memoryRecall
; ------------
;
; Read a previously stored frequency from a specified non-volatile
; memory area. Also reads the corresponding mode (USB/LSB/CW).
;
; Inputs: the required memory area is in 'lastkey', range 0 - 9.
;
; Output: daccc contains the new frequency.
; tmp contains the corresponding mode flags
;
; Postconditions: lastkey is preserved
;
;**********************************************************************
memoryRecall:
bcf _C
rlf lastkey, W ; W <- memory area * 2
movwf EEADR
rlf EEADR, F ; EEADR <- memory area * 4
; ...i.e. start address of relevant memory
movlw daccc ; Read data into daccc
movwf FSR
call eeread ; Read 32-bit value from EEPROM
movf daccc, W ; Top few bits of result contain mode flags
andlw MODE_MASK
movwf tmp
movlw ~MODE_MASK
andwf daccc, F ; Remove unwanted flags from 'freq'
retlw 0 ; Finished
;**********************************************************************
;
; set50Hz
; -------
;
; Sets dacca to a value of 50, ready to increment or decrement the
; frequency by that many Hz.
;
; Inputs: none
;
; Output: none
;
; Postconditions: dacca is set to the required value
;
;**********************************************************************
set50Hz:
clrf dacca
clrf dacca+1
clrf dacca+2
movlw .50
movwf dacca+3
retlw 0
;**********************************************************************
;
; set5KHz
; -------
;
; Sets dacca to a value of 5000, ready to increment or decrement the
; frequency by that many Hz.
;
; Inputs: none
;
; Output: none
;
; Postconditions: dacca is set to the required value
;
;**********************************************************************
set5KHz:
clrf dacca
clrf dacca+1
movlw HIGH .5000
movwf dacca+2
movlw LOW .5000
movwf dacca+3
retlw 0
;**********************************************************************
;
; set500KHz
; ---------
;
; Sets dacca to a value of 500000, ready to increment or decrement the
; frequency by that many Hz.
;
; Inputs: none
;
; Output: none
;
; Postconditions: dacca is set to the required value
;
;**********************************************************************
set500KHz:
clrf dacca
movlw .500000/10000
movwf dacca+1
movlw HIGH (.500000 % 10000)
movwf dacca+2
movlw LOW (.500000 % 10000)
movwf dacca+3
retlw 0
;
end