© 1999 QRP2000 Design Team

PIC Source code for the QRP2000 Controller

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