You are on page 1of 52

; ****************************************************************************

;
SweepGen - Swept Frequency Signal Generator using the AmQRP PIC-EL
;
board together with the NJQRP DDS daughtercard.
;
;
Version 1.1 October 4, 2005 by Bob Okas, W3CD (w3cd@arrl.net)
;
;
Description:
;
;
This is the control program for a DDS-based Sweep Generator using the
;
AmQRP PIC-EL board and DDS daughter card. It can be used as a fixed;
frequency signal generator or as a sweep frequency generator. The user
;
programs the start, end, step and marker frequencies.
;
;
SweepGen is based on PICELGen 2.01 by the same author. The following
;
comments are included to provide details of this program's forebears.
;
I indeed stand on the shoulders of giants.
;
;
Version 1.1 is a port of the previous version to the PIC16F628A.
;
Version 1.0 incorporates all of the improvements and changes from
;
the previous versions of PICELGen and modifies the original code base
;
to reduce program memory usage. Comments in the code are provided to
;
note these changes.
;
;
;******************************************************************************
; Original Author - Curtis W. Preuss - WB2V
;
; Modification History
;
8/19/98 - Version 1 - Initial Version by Curtis W. Preuss - WB2V
; 12/xx/98 - Version 2 - Converted to MPASM by Bruce Stough, AA0ED
;
4/21/99 - Version 3 - Fixed and modified by
;
Bruce Stough, AA0ED (sbs1@visi.com) and
;
Craig Johnson, AA0ZZ (cbjohns@cbjohns.com)
;
; 10/31/03 PICELgen1.0 Modify for PIC Elmer project by Craig Johnson, AA0ZZ
;
1) Change to use 1x8 LCD instead of 1x16
;
- Freq displayed as Hz (e.g. 14025000)
;
- CAL freq displayed as Hz (e.g. 10000000)
;
2) Set up to support the NJQRP DDS Daughterboard
;
- 50 MHz oscillator
;
3) Change PORTB pin allocations to support PIC-EL
;
- Change RB4-RB7 to RB0-RB3
;
- Change RB0 (DDS LOAD) to RB7
;
- Change RB1 (LCD_rs) to RB6
;
- Change RB2 (LCD_rw) to RB5
;
- Change RB3 (LCD_e) to RB4
;
- Change Busy_check routine to check correct bit
;
- Change cmnd2LCD/data2LCD routine - swap nibbles
;
4) Change RA2 to always be an LOW output
;
5) Support pushbutton on RA3 instead of encoder
;
shaft switch for bandswitch and calibrate
;
6) Support pushbutton on RA4 for fast tuning
;
;
1/3/04
PICELgen1.1 Add Title and Version on start-up
;
1/7/04
PICELgen1.2 Restructure main loop and change_band
;
2/2/04
PICELgen1.2a Fix confusing comment in the header re shaft sw.
;
Fix comment in header regarding PB_2 for calib.
;
2/8/04
PICELgen1.3 Fix for reliable startup of DDS
;
Add code for 1 MHz steps (up or down)
;
Add code to save new start-up frequency

;
3/12/04 PICELgen1.4 Remove use of watchdog timer (temp sensitivity)
;
Add code to support either 1x8 or 1x16 LCDs
;
- Use #DEFINE to select the LCD type
;
Fix calibrate routine so it stays in cal_loop
;
;
;
4/19/04 PICELgen2.0 Mods by Bob Okas, W3CD as follow:
;
;
1. Added the features listed at the top.
;
2. Fixed PORTA initialization bug that kept RA2
;
as an input, which caused the speaker to
;
continuously draw current.
;
3. Added LCD command definitions.
;
4. Put LCD messages in a table to reduce program
;
memory usage. Added message display function.
;
5. Modified sub_step to re-complement fstep_3..0
;
6. Rewrote pushbutton handler.
;
7. Added feature to display a message when
;
EEPROM contents are changed.
;
8. Changed default startup frequency to 7040 KHz.
;
9. Removed commented-out legacy code.
;
10. Changed hard-coded constants in program code
;
to named constants which appear at the top
;
of the program.
;
11. Added support for linearly addressed 16x1 LCDs
;
12. Removed the "#" characters preceeding IF, ELSE
;
and ENDIF directives to support other assemblers
;
besides MPASM.
;
13. Significant re-writes to optimize program memory
;
usage which are too numerous to mention here.
;
;
4/19/04 PICELgen2.01:
;
;
1. Fixed AmQRP 16x1 display and cursor addressing
;
bugs. Rearranged some initialization code. W3CD
;
; 10/4/05
SweepGen 1.1:
;
Ported ver 1.0 code to the 16F628A. W3CD
;
; 26/2/2012 SweepGen 1.1 Terry Mowles VK5TM
;
;
1. Updated to use AD8950 or AD9851. Currently set for AD9851
;
see code at end of calc_dds_word for change.
;
2. Ref Osc changed to 125MHz for AD9850 or 180MHz fo
r AD9851
;
3. Max frequency changed to 40MHz for AD9850, 60MHz
for AD9851
;
!!! COMMENT and UNCOMMENT code for respective chip !
!!
;
; 1/04/2013 SweepGen 1.1a
;
Added initialise DDS to zero freq at start up to prevent has
h
;
generation as per datasheet - Terry Mowles VK5TM
;
;*****************************************************************************
;
; Target Controller PIC16F628A in PIC-EL board
;
__________
;
PB_3-Speaker----RA2 |1
18| RA1---------ENCODER A

;
PB_2-Bandswitch-RA3 |2
17| RA0---------ENCODER B
;
PB_1-Tuning etc-RA4 |3
16| OSC1--------XTAL
;
+5V-----------!MCLR |4
15| OSC2--------XTAL
;
Ground----------Vss |5
14| VDD---------+5 V
;
LCD11-----------RB0 |6
13| RB7---------DDS_LOAD
;
LCD12-----------RB1 |7
12| RB6---------LCD_rs (LCD Pin 4)
;
LCD13/DDS_CLK---RB2 |8
11| RB5---------LCD_rw (LCD Pin 5)
;
LCD14/DDS_DATA--RB3 |9
10| RB4---------LCD_e (LCD Pin 6)
;
---------;
; ****************************************************************************
; *
Device type and options.
*
; ****************************************************************************
;
processor
PIC16F628A
radix dec
#include P16F628A.inc

; Include register and memory definitions

errorlevel 0, -205, -207, -220, -302 ; Skip nuisance messages


; ****************************************************************************
; * Configuration fuse information:
; ****************************************************************************
__config _CP_OFF & _LVP_OFF & _BODEN_OFF & _PWRTE_ON & _WDT_OFF & _XT_O
SC
;*****************************************************************************
;
;
PIC-EL Hardware Configurations
;
;*****************************************************************************
;
;
;
;
;
;
;
;
;

LCD Selections. The first batch of AmQRP PIC-EL boards were shipped with
8 character x 1 line LCDs. Later shipments used 16x1 LCDs which are
addressed as two 8x1 blocks. To get to the 9th character, the cursor
must be explicitly moved. There are other LCDs which employ linear display
addressing and no special actions are required to move from the 8th to the
9th character. These displays have been dubbed "linear" for the purposes of
this program. Select the appropriate #define to handle the appropriate LCD

;
;
;

Select only one of the following 3 #defines for your LCD type

;#define LCD_8
;#define LCD_16
#define LCD_16L
;
;
;

; Original 8 character AmQRP LCD


; Later AmQRP 16 character LCD
; Generic 16 character LCD with linear addressing

The following definitions depend upon the selection above.

IFDEF LCD_8
#define LCDCHAR 8
ENDIF

; AmQRP 8x1 LCDs -- Shipped with earlier kits


; Enable 8-character LCD program features

IFDEF LCD_16
; AmQRP 16x1 LCDs -- Shipped with later kits
#define LCDCHAR 16
; Enable 16-character LCD program features
#define LCD16_LINEAR 0 ; Use for AmQRP 16x1 displays
ENDIF
IFDEF LCD_16L
; Alternative, linearly addressed 16x1 displays
#define LCDCHAR 16
; Enable 16-character LCD program features
#define LCD16_LINEAR 1 ; Use linear addressing
ENDIF
;
; Defining DETENT_ENCODER enables software to debounce the PIC-EL mechanical
; encoder and modifies the incrementing so that only one count changes at
; each detent. Comment out the line below for optical or detent-less encoders
#define DETENT_ENCODER
;
; The Software Version number is kept here
;
#define CODE_VERSION "1.1 "
;
; *****************************************************************************
; * DDS Frequency control equates. These may be changed to accommodate the *
; * reference clock frequency and the desired upper frequency limit frequency.*
*
; *****************************************************************************
;
; ref_osc represents the change in the frequency control word which results
; in a 1 Hz change in output frequency. It is interpreted as a fixed point
; integer in the format <ref_osc_3>.<ref_osc_2><ref_osc_1><ref_osc_0>
;
; The values for common oscillator frequencies are as follows:
;
; Frequency
ref_osc_3
ref_osc_2
ref_osc_1
ref_osc_0
;
; 180.00 MHz
0x17
0xDC
0x65
0xDF
; 125.00 MHz
0x22
0x5C
0x17
0xCA
; 120.00 MHz
0x23
0xCA
0x98
0xCE
; 100.00 MHz
0x2A
0xF3
0x1D
0xC4
; 90.70 MHz
0x2F
0x5A
0x82
0x7A
; 66.66 MHz
0x40
0x6E
0x52
0xE7
; 66.00 MHz
0x41
0x13
0x44
0x5F
; 50.00 MHz
0x55
0xE6
0x3B
0x88
;
; To calculate other values:
;
ref_osc_3 = (2^32 / oscillator_freq_in_Hertz).
;
ref_osc_2, ref_osc_1, and ref_osc_0 are the fractional part of
;
(2^32 / oscillator_freq_in_Hertz) times 2^24.
;
Note: 2^32 = 4294967296 and 2^24 = 16777216
;

; For example, for a 120 MHz clock:


;
ref_osc_3 is (2^32 / 120 x 10^6) = 35.791394133 truncated to 35 (0x23)
;
ref_osc_2 is the high byte of (.791394133 x 2^24) = 13277390.32
;
13277390.32 = 0xCA98CE, so high byte is CA.
;
ref_osc_1 is the next byte of 0xCA98CE, or 98
;
ref_osc_0 is the last byte of 0xCA98CE, or CE
;
;==== Currently set for 100 MHz Oscillator =======
;ref_osc_3
;ref_osc_2
;ref_osc_1
;ref_osc_0

equ
equ
equ
equ

0x2A
0xF3
0x1D
0xC4

;
;
;
;

Most significant osc byte


Next byte
Next byte
Least significant byte

;==== Currently set for 125 MHz Oscillator ======= Modified 22/2/12 TM
;ref_osc_3
;ref_osc_2
;ref_osc_1
;ref_osc_0

equ
equ
equ
equ

0x22
0x5C
0x17
0xD0

; Most significant osc byte


; Next byte
; Next byte
; Least significant byte

;==== Currently set for 180 MHz Oscillator ======= For AD9851 Modified 22/2/12 T
M
ref_osc_3
ref_osc_2
ref_osc_1
ref_osc_0
;
;
;
;

equ
equ
equ
equ

0x17
0xDC
0x65
0xDF

; Most significant osc byte


; Next byte
; Next byte
; Least significant byte

limit3..0 specify the upper limit frequency (in Hz) expressed as a 32 bit
integer. This should not be set to more than one third of the reference
oscillator frequency. The output filter of the DDS board must be designed
to pass frequencies up to the maximum.

;limit_3
;limit_2
;limit_1
;limit_0

equ
equ
equ
equ

0x01
0xC9
0xC3
0x80

;
;
;
;

Most significant byte for 30 MHz


Next byte
Next byte
Least significant byte

;------------------------------------------------------------------------------------------------------; Modified 22/2/12 TM - 40MHz upper Frequency limit (AD9850)


; limit3..0 specify the upper limit frequency (in Hz) expressed as a 32 bit
; integer. This should not be set to more than one third of the reference
; oscillator frequency. The output filter of the DDS board must be designed
; to pass frequencies up to the maximum.
;limit_3
equ 0x02
; Most significant byte for 40 MHz
;limit_2
equ 0x62
; Next byte
;limit_1
equ 0x5A
; Next byte
;limit_0
equ 0x00
; Least significant byte
;-------------------------------------------------------------------------------------------------------;------------------------------------------------------------------------------------------------------; Modified 22/2/12 TM - 60MHz upper Frequency limit (AD9851)
; limit3..0 specify the upper limit frequency (in Hz) expressed as a 32 bit
; integer. This should not be set to more than one third of the reference

; oscillator frequency. The output filter of the DDS board must be designed
; to pass frequencies up to the maximum.
limit_3
equ 0x03
; Most significant byte for 60 MHz
limit_2
equ 0x93
; Next byte
limit_1
equ 0x87
; Next byte
limit_0
equ 0x00
; Least significant byte
;-------------------------------------------------------------------------------------------------------; The following is the definition of the 10.00000 MHz
; calibration frequency
cal_freq_0
cal_freq_1
cal_freq_2
cal_freq_3

equ
equ
equ
equ

0x80
0x96
0x98
0x00

; LSB
; MSB

;*******************************************************************************
******
;
;
General definitions
;
;*******************************************************************************
******
EEPROM_BASE

equ 0x2100

; Where EEPROM begins in the 16F84A

RAM_BASE
6F84A

equ 0x20

; Where General Purpose Regs. begin in 1

RAM_ALL
pages

equ 0x70

; Origin in BANK 0 of ram accessable across all

TABLE_ADJUST

equ 3

; Value to adjust table pointers

MSB

equ 7

; Most significant bit in a byte

UP_BIT

equ 0x02

DIR_BIT

equ 1

; Used by poll_encoder to determine direction

FLAG_MASK

; Bit to test for shaft encoder direction


equ 0xff

; Mask used to set status flags.

LOW_NYBBLE_MASK equ 0x0f

; Mask used to extract low 4 bits in a byte

HI_NYBBLE_MASK equ 0xf0

; Mask used to extract high 4 bits in a byte

ASCII_NUM_BASE equ 0x30

; Added to BCD digits to get printable digits

ENCODER_BITS
ion

equ 0x03

; Mask applied to PORTA to extract encoder posit

TEN

equ 10 ;

VFO_SEL

equ 0

FREQ_COUNT

; VFO A/B select bit


equ 4 ; Number of frequency bytes to copy or loop over

DECADE_MASK

equ 0x07

; Applied to decade adjustments

SMALL_FSTEP

equ 0x04

; Small cal. freq. step. About .4 Hz

LARGE_FSTEP

equ 0x80

; Large cal. freq. step. About 3 Hz

PORTA_CONTROL

equ 0xfb

DECADE_0
DECADE_1
DECADE_2
DECADE_3
DECADE_4
DECADE_5
DECADE_6
DECADE_7

equ
equ
equ
equ
equ
equ
equ
equ

; Port A is all inputs except RA2


0
1
2
3
4
5
6
7

;
;
;
;
;
;
;
;

1 Hz adjustment value
10 Hz adjustment value
100 HZ
1 KHz
10 KHz
100 KHz
1 MHz
10 MHz

;*******************************************************************************
*******
;
PB_flags bits and related definitions
;
;
These bits define the operation of PB_1 and PB_2 during startup and norm
al
;
operations.
;
;*******************************************************************************
*******
PB_RPT_FLG
PB_CS_FLG
PB_1_PRESS
PB_2_PRESS
F_SET

equ 4

equ 0 ; Indicates that PB was repeating


equ 1 ; Indicates that cal or sweep mode is enabled
equ 2 ; Indicates that PB_1 was pressed
equ 3 ; Indicates that PB_2 was pressed
; Indicates that PB_2 were pushed

DECADE_DECREMENT
y decade
DECADE_INCREMENT
y decade

equ 0xff

; Value which decrements cursor frequenc

equ 0x01

; Value which increments cursor frequenc

PB_RPT_WAIT
PB_RPT_DLY
PB_INIT_FREQ_WAIT

equ 32 ; 1 second delay before autorepeat


equ 4 ; 1/8 second between repeats
equ 64 ; 2 second delay before new start freq set

;*******************************************************************************
*******
;
;
PIC-EL LED Control bits and related definitions
;
;*******************************************************************************
*******
LED1_ON
LED2_ON
LED3_ON

equ 0x07
equ 0x0b
equ 0x0d

; Mask AND'd with PORTB to turn on LED1


; Mask AND'd with PORTB to turn on LED2
; Mask AND'd with PORTB to turn on LED3

LED1_OFF
LED2_OFF
LED3_OFF

equ 0x08
equ 0x04
equ 0x02

; Value OR'd with PORTB to turn off LED1


; Value OR'd with PORTB to turn off LED2
; Value OR'd with PORTB to turn off LED3

LEDS_OFF
LED_MASK
s

equ 0x0e
equ 0xf1

; Port B bits to turn off all LEDs


; Port B mask to isolate led control bit

LED1_2_ON
LED1_3_ON
LED2_3_ON

equ LED1_ON & LED2_ON


equ LED1_ON & LED3_ON
equ LED2_ON & LED3_ON

;*****************************************************************************
;
;
LCD Command Definitions
;
; The following are a series of command definitions and parameters that the
; Hitatchi 44780 LCD controller chip recognizes. Some commands have parameters
; which may be or'ed into the basic data to form a single-byte command. In
; such cases, these parameters have been grouped directly underneath the basic
; command data and bear descriptive names and comments related to their
; functions.
;
;*****************************************************************************
CMD_CLEAR_LCD

equ 0x01

; Clears the Display

CMD_RESET_LCD

equ 0x03

; Resets the display. Write this 3 times

CMD_SET_IFC_1
IFC1_4_BIT
IFC1_8_BIT

equ 0x02

PICEL_IFC1
CMD_SET_IFC_2
IFC2_4_BIT
IFC2_8_BIT
IFC2_1_LINES
IFC2_2_LINES
IFC2_FONT_5_7
IFC2_FONT_5_10

; Interface programming word 1


; 4 bit I/O with LCD
; 8-bit I/O (not used with PIC-EL)

equ 0x00
equ 0x01

equ CMD_SET_IFC_1 + IFC1_4_BIT


equ 0x20

; Interface programming word 2


; 4-bit I/O
; 8-bit I/O (not used with PIC-EL)
; Set display to 1 line of characters
; Set display to 2 lines of characters
; Sets display font to 5x7 dot character matrix
; Sets display font to 5x10 dot character matrix

equ 0x00
equ 0x10
equ
equ
equ
equ

0x00
0x08
0x00
0x04

IF LCDCHAR == 16
IF LCD16_LINEAR == 0
PICEL_IFC2
T_5_7
ELSE
PICEL_IFC2
T_5_7
ENDIF
ELSE
PICEL_IFC2
T_5_7
ENDIF

equ CMD_SET_IFC_2 + IFC2_4_BIT + IFC2_2_LINES + IFC2_FON


equ CMD_SET_IFC_2 + IFC2_4_BIT + IFC2_1_LINES + IFC2_FON

equ CMD_SET_IFC_2 + IFC2_4_BIT + IFC2_2_LINES + IFC2_FON

CMD_DISP_CUR_EN equ 0x08


; Base command for display & cursor control
DISP_OFF
equ 0x00
; Disables display

DISP_ON
CUR_OFF
CUR_ON
BLINK_OFF
BLINK_ON

equ 0x04
equ 0x00
equ 0x02

; Enables display
; Disables cursor
; Enables underline cursor
equ 0x00
; Disables blinking block cursor
equ 0x01
; Enables blinking block cursor

PICEL_DISPLAY_OFF
PICEL_DISPLAY_ON

equ CMD_DISP_CUR_EN + DISP_OFF + CUR_OFF + BLINK_OFF


equ CMD_DISP_CUR_EN + DISP_ON + CUR_ON

CMD_HOME_CUR_LCD

equ 0x02

; Return Display and cursor to home pos.

CMD_SET_INC
equ 0x04
; Sets Display and cursor movement
INC_DISPLAY_POS equ 0x01
; Shifts the display after data write
INC_CURSOR_POS equ 0x02
; Increments the cursor along with display
PICEL_CURSOR_RT equ CMD_SET_INC + INC_CURSOR_POS
CMD_SET_DSP_CUR equ 0x08
; Controls Display and cursor
ENABLE_BLOCK_BLINK
equ 0x01
; Blinking Block if enabled
ENABLE_CURSOR equ 0x02
; Underline if not blinking block
ENABLE_DISPLAY equ 0x04
; Turns on Display
CMD_SET_SCROLL equ 0x10
SCROLL_ENABLE equ 0x08
SCROLL_RIGHT
equ 0x04

; Controls Display/Cursor Scrolling


; Enables Display/Cursor Scrolling
; Enables Right scroll. Otherwise it's Left.

CMD_CURSOR_CGRAM

equ 0x40
; Move cursor position within character
; generator memory. "OR" in the CGRAM
; address to this command. Valid range
; is 0x00 - 0x3f

CMD_MOV_CUR_DISP

equ 0x80
; Move cursor position within Display
; "OR" in the Display position to this command.
; 0x00 is the leftmost position.
equ 0x40
; Offset added to above command to posit

OFFSET_9
ion

; cursor at 9th character. Used for AmQRP


; 16 character LCDs.
PICEL_LONG_9

equ CMD_MOV_CUR_DISP + OFFSET_9

LCD_POS_9

equ 8

; Cursor position 9 on 16 character LCDs

;
; ****************************************************************************
; * Assign names to IO pins.
*
; ****************************************************************************
;
;
;

B register bits:

DDS_clk
DDS_dat
LCD_busy
LCD_e
LCD_rw
LCD_rs
DDS_load

equ 0x02
equ 0x03

;
;
equ 0x03
equ 0x04
;
equ 0x05
;
equ 0x06
;
equ 0x07

AD9850 write clock


AD9850 serial data input
; LCD busy bit
0=disable, 1=enable
0=write, 1=read
0=instruction, 1=data
; Update pin on AD9850

;
;
;

A register bits:

MARKER
PB_3
PB_2
PB_1
;
;
;
;
;
;

equ
equ
equ
equ

;
;
;
;

Tones bit = RA2


PB also on this pin. Not used in SweepGen
Multi-purpose pushbutton
Tuning-increment/Calibrate Pushbutton

****************************************************************************
* ID location information:
*
* (MPASM warns about DW here, don't worry)
*
****************************************************************************
ORG
DATA
DATA
DATA
DATA

;
;
;
;

0x02
0x02
0x03
0x04

0x2000
0x57
0x33
0x43
0x44

****************************************************************************
* Setup the initial constant, based on the frequency of the reference
*
* oscillator. This can be tweaked with the calibrate function.
*
****************************************************************************
ORG

EEPROM_BASE

; ref_osc bytes appear as the first 4 bytes of EEPROM


EE_ref_osc_loc
DATA
ref_osc_0, ref_osc_1, ref_osc_2, ref_osc_3
;*****************************************************************************
;
;
Programmable Frequency Memories. The following EEPROM locations
;
store several frequency variables. Data is expressed in Hz and
;
is stored LSB first.
;
;*****************************************************************************
EE_cal_freq_loc
DATA
cal_freq_0, cal_freq_1, cal_freq_2, cal_freq_3
EE_start_freq_loc
DATA
0x80, 0x84, 0x1e, 0x00

; 2.000 MHz

EE_end_freq_loc
DATA
0x20, 0x0b, 0x20, 0x00

; 2.100 MHz

EE_step_freq_loc
DATA
0x64, 0x00, 0x00, 0x00

; 100 Hz Step

EE_marker_freq_loc
DATA
0xd0, 0x47, 0x1f, 0x00

; Marker @ 2.05 MHz

;
;
;
;

Develop offsets from start of EEPROM for use


by the program

EE_ref_osc
EEcal_freq
EEstart_freq
EEend_freq
EEstep_freq
EEmarker_freq

equ
equ
equ
equ
equ
equ

EE_ref_osc_loc
EE_cal_freq_loc
EE_start_freq_loc
EE_end_freq_loc
EE_step_freq_loc
EE_marker_freq_loc

EEPROM_BASE
EEPROM_BASE
EEPROM_BASE
EEPROM_BASE
EEPROM_BASE
EEPROM_BASE

;
; ****************************************************************************
; *
Allocate variables in general purpose register space
*
; ****************************************************************************
;
CBLOCK RAM_BASE
; Start Data Block
BCD_0
BCD_1
BCD_2
BCD_3
BCD_4

; Display frequency (BCD)


; (5 bytes)

temp_freq_0
temp_freq_1
temp_freq_2
temp_freq_3
osc_0
osc_1
osc_2
osc_3

;
;
;
;

; Current oscillator
; (4 bytes)

AD9850_0
AD9850_1
AD9850_2
AD9850_3
AD9850_4
fstep_0
fstep_1
fstep_2
fstep_3

Temporary frequency storage


(4 bytes)
Used by bin2BCD, calibrate
and calc_dds_word

; AD9850/51 control word


; (5 bytes)

;
;
;
;

Frequency inc/dec
(4 bytes)
update_decade fills these in
with values from decade_table

start_freq_0
start_freq_1
start_freq_2
start_freq_3

; Sweep start frequency

end_freq_0
end_freq_1
end_freq_2
end_freq_3

; Sweep end frequency

step_freq_0
step_freq_1
step_freq_2
step_freq_3

; Frequency Step size

marker_freq_0
marker_freq_1
marker_freq_2
marker_freq_3

; Frequency at which to set RA2

BCD_count
BCD_temp

; Used in bin2BCD routine


; "

bit_count
byte2send

; Used in calc_dds_word
;

LCD_char
LCD_read

; Character being sent to the LCD


; Character read from the LCD

timer1
timer2

; Used in delay routines


; "

ren_new
ren_old
ren_read
last_dir
next_dir

; New value of encoder pins A and B


; Old value of encoder pins A and B
; Encoder pins A and B and switches
; Indicates last direction of encoder
; Indicates expected direction

count

; Multipurpose loop counter

rs_value

; The LCD rs line flag value

PB_flags
PB_wait_count

; Pushbutton flags
; PB repeat control timer

temp

; Miscellaneous variable

decade
cur_pos

; Frequency decade subject to updating


; Cursor position: 0-7 for 8 char LCD

fstruct_ptr

; Pointer to frequency data struct in pgm memory

message_pointer

; Temp message pointer variable

enc_counter

; Divide by 4 counter for mechanical encoder

freq_pointer

; pointer to current frequency being modified.

led_states

; Used to store which leds are on.

ENDC
CBLOCK RAM_ALL
ee_addr
ENDC

; End of Data Block


; Common RAM base
; Storage for eeprom address

;
;
;
;
;

****************************************************************************
* The 16F84 resets to 0x00.
*
* The Interrupt vector is at 0x04. (Unused)
*
****************************************************************************

;
ORG
reset_entry
goto

0x0000
start

; Jump around the band table to main program

;****************************************************************************
;
;
Decade Table
;
; Each entry is four instructions long, with each group of four literals
; representing the frequency as a 32 bit integer. Each four byte entry
; represents the decade increment which can get be assigned to
; fstep_3, fstep_2, fstep_1 and fstep_0. Data is stored MSB first.
;
;****************************************************************************
decade_table
addwf
dt
dt
dt
dt
dt
dt
dt
dt

PCL,f
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,

; Jump to the appropriate value to return


0x00,
0x00,
0x00,
0x00,
0x00,
0x01,
0x0f,
0x98,

0x00,
0x00,
0x00,
0x03,
0x27,
0x86,
0x42,
0x96,

0x01
0x0a
0x64
0xe8
0x10
0xa0
0x40
0x80

;
;
;
;
;
;
;
;

1Hz increment
10 Hz increment
100 Hz increment
1 KHz increment
10 KHz increment
100 KHz increment
1 MHz increment
10 MHz increment

;****************************************************************************
;
;
Frequency Data Structures
;
; Collecting the data for the sweep parameters is so repetitious that
; it made sense to write just one function to do the actual data collection,
; thus saving program space. The particulars of the data to collect are
; stored in what amounts to an array of structures, a look-up table if you
; will, that contains the collection function parameters. Each structure
; contains 3 elements:
;
1. Which LED(s) to illuminate
;
2. What message to flash on the LCD
;
3. What decade to start the frequency adjustment
;
4. Starting RAM location used to hold the frequency
;
;****************************************************************************
LED_OFFSET
MSG_OFFSET
DEC_OFFSET
RAM_OFFSET

equ
equ
equ
equ

0
1
2
3

;
;
;
;

Offset
Offset
Offset
Offset

to
to
to
to

LED data struct element


prompt msg pointer element
decade element
frequency storage

f_struct_table
addwf PCL,f

; Jump to the appropriate table entry to return

f_start_struct_loc
dt
LED1_ON, start_freq_msg, DECADE_3, start_freq_0
f_end_struct_loc
dt
LED2_ON, end_freq_msg, DECADE_3, end_freq_0
f_step_struct_loc
dt
LED1_2_ON, step_freq_msg, DECADE_0, step_freq_0
f_marker_struct_loc
dt
LED3_ON, marker_freq_msg, DECADE_3, marker_freq_0
;
; Prepare the table offsets for use by the program
;
f_start
f_end
f_step
f_marker

equ
equ
equ
equ

f_start_struct_loc
f_end_struct_loc
f_step_struct_loc
f_marker_struct_loc

f_start_struct_loc
f_start_struct_loc
f_start_struct_loc
f_start_struct_loc

;****************************************************************************
;
;
Cursor Positioning Table
;
; 8 and 16 character LCD displays differ, so cursor positioning for
; frequency displays is put into tables to simplify the software.
;
;****************************************************************************
cursor_table
addwf

PCL,f

; Jump to the appropriate value to return

IF LCDCHAR == 8
dt
7, 6, 5, 4, 3, 2, 1, 0
ELSE
dt
10, 9, 8, 6, 5, 4, 2, 1
ENDIF
;****************************************************************************
;
; Message Table. All of the PIC-EL LCD messages are stored here. The last
; byte of the message string is set to 0 to indicate the end of string. The
; messages are indexed via an offset value, which is calculated at assembly
; time.
;
;****************************************************************************
message_table
addwf

PCL,f

; Calculate the return value address


; and jump to the appropriate character

messages
sign_on_msg_loc

; Displayed at power-on

dt

"SweepGen", 0

ver_msg_loc

; Displayed at power-on

IF LCDCHAR == 8
dt
"Ver ", CODE_VERSION, 0
ELSE
dt
" Ver", CODE_VERSION, 0
ENDIF
start_freq_msg_loc
dt
"Start", 0
end_freq_msg_loc
dt
"End", 0
step_freq_msg_loc
dt
"Step", 0
marker_freq_msg_loc
dt
"Marker", 0
sweep_msg_loc
dt
"Sweeping",0
end_err_msg_loc
dt
"Bad End",0
;
;
;

Develop offsets to messages in table

sign_on_msg
ver_msg
start_freq_msg
end_freq_msg
step_freq_msg
marker_freq_msg
sweep_msg
end_err_msg

equ
equ
equ
equ
equ
equ
equ
equ

sign_on_msg_loc
ver_msg_loc
start_freq_msg_loc
end_freq_msg_loc
step_freq_msg_loc
marker_freq_msg_loc
sweep_msg_loc
end_err_msg_loc

messages
messages
messages
messages
messages
messages
messages
messages

IF LCDCHAR == 16
khz_msg_loc
dt

" KHz",0

calibrate_msg_loc
dt
"CAL ", 0
khz_msg
cal_msg

; KHz display for 16 char lcd only


; Displayed during CAL mode only
; and only on 16 character LCDs

equ khz_msg_loc
- messages
equ calibrate_msg_loc - messages

ENDIF

;*****************************************************************************
;

;
Purpose: This is the start of the program. Program and hardware
;
initialization takes place here before the execution falls
;
into the main program loop.
;
;
Input: PB_1 determines if calibration is to be performed.
;
;
Output: None.
;
;
Revisions:
;
Ver 1.0: Copied from PICELGen 2.0 and modified for use here
;
4-20-04 W3CD.
;
Ver 1.1: Adapted for PIC16F628A. 10-04-05 W3CD
;
;*****************************************************************************
start
clrf
movlw
movwf
clrf

INTCON
0x07
CMCON
RCSTA

;
;
;
;

No interrupts for now


Set the 3 LSBs of CMCON
to enable normal I/O
Disable USART Receiver

bsf
clrf
clrf
clrf

STATUS,RP0
TXSTA
PIE1
VRCON

;
;
;
;

Switch to bank 1
Disable USART Transmitter
Clear all interrupt enable bits
Disable voltage reference

bcf OPTION_REG,NOT_RBPU ; Enable weak pullups


movlw PORTA_CONTROL
; Set Port A bits.
movwf TRISA
;
clrf
TRISB
; Set port B to all outputs
bcf
STATUS,RP0
; Switch back to bank 0
; Initialize DDS Module with zero freq
clrf
clrf
clrf
clrf
clrf
call
call

AD9850_0
AD9850_1
AD9850_2
AD9850_3
AD9850_4
send_dds_word
send_dds_word

; AD9850/51 control word


; (5 bytes)

bsf

PORTA,MARKER

; Set marker bit high

call
movlw
movwf
call

init_LCD
LEDS_OFF
led_states
display_version

; Initialize the LCD


; Turn off LEDS at the start

;
; Read reference oscillator data
;
movlw osc_0
movwf FSR
movlw EE_ref_osc
call
read_EEPROM_freq

; Send it to the DDS


; twice to be sure

;
; Initialize other variables.
;

; Display title and version number


from EEPROM
;
;
;
;

Get osc_0 address


save for indirect addressing
Get EEPROM ref_osc offset into W
Read the EEPROM and save in osc bytes

clrf
clrf
;
; Get the power
;
movf
movwf
movlw
andwf
movwf

last_dir
PB_flags

; Clear the knob direction indicator


; Clear the flags

on encoder value.
PORTA,w
ren_read
ENCODER_BITS
ren_read,w
ren_old

;
;
;
;
;

Read port A
Save it in ren_read
Get encoder mask (RA0 and RA1)
Get encoder bits
Save in ren_old

;
; Enter Calibrate Mode if push button is pressed while applying power
;
btfss
call
call

ren_read,PB_1
calibrate

;
;
;
read_sweep_freqs ;
;

Tuning-increment/Cal pushbutton pressed?


Yes, calibrate
Else, continue with rest of initialization
Read the stored frequencies from EEPROM
and set freq_pointer to start_freq_0

; *****************************************************************************
;
;
Function: main
;
;
Purpose: This is the Main Program Loop. This section collects the
;
starting and ending sweep frequencies, the frequency increment
;
and the marker frequency from the user. This information is
;
then stored in EEPROM for later use. When all of the parameters
;
have been collected, the sweep function is called to perform
;
the frequency sweeps. When the user presses the appropriate
;
button on the PICEL board, the sweep terminates and control
;
returns back to main.
;
;
Input: No explicit inputs.
;
;
Output: No explicit outputs.
;
;
Revisions:
;
Ver 1.0 New function 4-20-04 W3CD
;
;*********************************************************************
main
movlw
call

f_start
get_frequency

; Starting frequency structure


; Get the starting frequency

movlw
call
call
btfss
goto

f_end
get_frequency
check_end
STATUS,C
get_step

call
movlw
call
call

clear_lcd
end_err_msg
display_message
wait_512ms

;
;
;
;
;
;
;
;
;
;

get_end
Ending frequency structure
Get the ending frequency
Make sure that end > start
If carry is set, then all's well.
Go get the next parameter
Else, there is a problem.
Erase LCD
Flash error message
Display for 1/2 second

goto

get_end

; Go back and try again.

movlw
call

f_step
get_frequency

; Step size structure


; Collect the data

movlw
call

f_marker
get_frequency

; Marker frequency structure


; Collect that information

call
call

write_sweep_freqs
sweep

; Store the user-specified freqs in EEPROM


; Sweep until button is pressed

call
call
goto

read_sweep_freqs
get_decade
main

; Read the sweep frequencies from EEPROM


; Restore fstep3..0
; Go back Jack and do it again.

get_step

;******************************************************************************
;
;
Function: get_frequency
;
;
Purpose: Get the user-specified frequency
;
;
Inputs: W points to the data structure which contains the
;
pertinent data
;
User enters the frequency using the shaft encoder.
;
PB3 is pressed to accept the displayed value.
;
;
Outputs: The LCD displays the current frequency.
;
The DDS is updated as the frequency selection changes.
;
X_freq_3..0 contain the current frequency data when
;
PB3 is pressed.
;
;
Revsisions:
;
Ver 1.0: New function 4-21-04 W3CD
;
;*******************************************************************************
get_frequency

movwf
call
movwf
call
call

fstruct_ptr
f_struct_table
led_states
set_leds
clear_lcd

;
;
;
;
;

Save pointer to current frequency data


Access the table to return LED data
Copy w to led_states and
set the appropriate LED(s)
Wipe the display

movf
addlw
call
call
call

fstruct_ptr,w
MSG_OFFSET
f_struct_table
display_message
wait_512ms

movf
addlw
call
movwf
clrw
call

fstruct_ptr,w
DEC_OFFSET
f_struct_table
decade

;
;
;
;
;
;
;
;
;
;
;
;

Get the struct pointer


Add in the message pointer offset
Get the pointer to the prompt message
Display the prompt message
Show if for a while
Now get the default adjustment decade
Get the struct pointer
Add offset to point to decade to adjust
Retreive the value
Store in decade variable
Prepare W for decade adjust
Position cursor and select decade incremen

update_decade

call
movf
addlw
call
movwf

clear_lcd
fstruct_ptr,w
RAM_OFFSET
f_struct_table
freq_pointer

;
;
;
;
;

call
call
call

show_freq
update_dds
send_dds_word

; Show the frequency


; Send it to the DDS
; twice to be sure

get_freq_1
call
btfsc
goto

poll_encoder
PB_flags,F_SET
get_freq_2

call
goto

step
get_freq_1

get_freq_2
bcf
PB_flags,F_SET
return

;
;
;
;
;
;

Erase contents
Get the struct pointer
Add in frequency data offset
Get the pointer to frequency data storage
Save this for the following operations

Wait for user to do something


Check if PB was pushed.
If so, then finish up
Else, encoder was adjusted
Adjust the frequency
Loop until setting is committed.

; Clear the Freq Set flag

;******************************************************************************
;
;
Function: check_end
;
;
Purpose: Compares the start and end frequencies. If end freq is
;
greater than start freq, the Carry flag is clear. Otherwise,
;
it is set, indicating that end_freq <= start_freq.
;
;
Inputs: start_freq and end_freq contain the user-specified data
;
;
Outputs: Carry flag
;
;
Revsisions:
;
Ver 1.0: New function 4-26-04 W3CD
;
;******************************************************************************
check_end
movf
movwf
movf
movwf
movf
movwf
movf
movwf

start_freq_0,w
AD9850_0
start_freq_1,w
AD9850_1
start_freq_2,w
AD9850_2
start_freq_3,w
AD9850_3

movlw end_freq_3
movwf FSR
call
check_freq_status
return

;
;
;
;
;
;
;
;
;
;
;
;
;

Copy the end_freq values


into AD9850 registers to
perform the comparison.
This doesn't bother the
DDS data because it will
be recalculated before
these registers are
needed again.
Point to the start_freq MSB
Tuck it away into FSE
Calculate start_freq - end_freq
Return the Carry flag.

;******************************************************************************
;
;
Function: sweep

;
;
Purpose: Produces a swept frequency output from start_freq to end_freq.
;
It also sets high the PIC RA2 output bit when the marker
;
frequency has been met and exceeded. This marker bit is cleared
;
when the sweep returns to start_freq.
;
;
This function precalculates the DDS data for start_freq,
;
end_freq, step_freq and marker_freq to avoid using calc_dds_word
;
in the loop as this function is costly in terms of time.
;
;
Inputs: start_freq_3..0, end_freq_3..0, step_freq_3..0,
;
marker_freq_3..0, PB_flags
;
;
Outputs: DDS produces swept frequency output until the user presses the
;
appropriate pushbutton to stop.
;
;
Revisions:
;
Rev 1.0: New function. 4-21-04 W3CD
;
;*******************************************************************************
sweep
bsf

; Prepare for sweeping


PB_flags,PB_CS_FLG ; Set Cal/Sweep flag to skip some PB handlin

movlw
movwf
call
movlw
call

step_freq_0
freq_pointer
calc_dds_word
fstep_0
copy_dds_word

;
;
;
;
;
;

movlw
call

end_freq_0
convert_to_dds

; Get address of end frequency


; Convert to DDS and store it back

movlw
call

marker_freq_0
convert_to_dds

; Get address of marker frequency


; Convert to DDS & store it back

call
movlw
call

clear_lcd
sweep_msg
display_message

; Inform the user that sweeping is


; begun.

g
Get frequency increment address
Store it in freq_pointer
Convert it to DDS data in AD9850_3..0
Copy target address is fstep_3..0
Copy AD9850_3..0 to fstep

sweep_start
bcf
movlw
movwf
call

PORTA,MARKER
start_freq_0
freq_pointer
calc_dds_word

movlw
movwf

AD9850_0
freq_pointer

sweep_loop
call
btfsc
goto

pb_2_check
PB_flags,F_SET
sweep_end

;
;
;
;
;
;
;
;

Where the sweeping begins


Clear the marker bit to start
Get the starting frequency pointer
Store it in freq_pointer
Calculate the DDS word
AD9850_4..0 are our working registers,
so copy start address to freq_pointer

Examine user input.


Check if user requested end of sweeping
If so, then exit
Else, do another iteration
Send the frequency to DDS

call

send_dds_word

;
;
;
;
;

movlw

marker_freq_3

; Get last address of marker frequency

movwf
call
btfsc

FSR
check_freq_status
STATUS,C

; into FSR
; Check if it's time to raise the marker
; If carry is clear, then marker freq reache

bsf

PORTA, MARKER

; Set marker to indicate that freq was reach

call

add_step

movlw
movwf
call
btfsc
goto
goto

end_freq_3
FSR
check_freq_status
STATUS,C
sweep_start
sweep_loop

;
;
;
;
;
;
;
;

d
ed

sweep_end
clrf
PB_flags
bsf
PORTA,MARKER
return

Add the frequency increment


to current AD9850 data
Point to the ending DDS frequency word
Store in FSR
test if we've reached the end limit
If carry clear, then
end was reached. Restart the sweep
Else, loop again

; Clear the PB flags on exit


; Set the marker bit high at end

;******************************************************************************
;
;
Function: convert_to_dds
;
;
Purpose: Converts the 4-byte frequency value pointed to by W
;
into DDS-compatible data and store it back into the
;
supplied frequency address. Support function for sweep.
;
;
Inputs: W points to X_freq_0
;
;
Outputs: X_freq_3..0 contain the DDS-compatible data
;
;
Revisions:
;
Rev 1.0: New function. 4-21-04 W3CD
;
;*******************************************************************************
convert_to_dds
movwf
call

freq_pointer
calc_dds_word

; Store address in W into freq pointer


; Convert freq to DDS word
; AD9850_3..0 are relevant since _4 is alway

s 0
movf
freq_pointer,w
call
copy_dds_word
return

;
;
;
;

Copy this data to X_freq_3..0


Get the target address of copy
Copy the DDS data
Back to caller

;******************************************************************************
;
;
Function: check_freq_status
;
;
Purpose: Compares the frequency registers pointed to by FSR against
;
the AD98503..0 bytes.
;
;
Inputs: FSR points to X_freq_3 data to be tested
;
AD9850_3..0 contain the current DDS word

;
;
Outputs: Carry flag is set if X_freq_3..0 >= AD9850_3..0,
;
Else, Carry is clear
;
;
Revisions:
;
Rev 1.0: New function. 4-22-04 W3CD
;
;*******************************************************************************
check_freq_status
movf
subwf
btfss
goto

INDF,w
AD9850_3,w
STATUS,C
check_freq_end

btfss
goto

STATUS,Z
check_freq_end

decf
movf
subwf
btfss
goto

FSR,f
INDF,w
AD9850_2,w
STATUS,C
check_freq_end

btfss
goto

STATUS,Z
check_freq_end

decf
movf
subwf
btfss
goto

FSR,f
INDF,w
AD9850_1,w
STATUS,C
check_freq_end

btfss
goto

STATUS,Z
check_freq_end

decf
movf
subwf

FSR,f
INDF,w
AD9850_0,w

check_freq_end
return

;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;

Get the X_freq_3 value into w


W = AD9850_3 - X_freq_3
Check Carry
If clear, then freq_3 < AD_3. Done
Else, Carry set, so
are the bytes equal?
No, so freq_3 > AD_3. Done
Else, both MSBs are equal
Point to X_freq_2
W <- X_freq_2
W = AD9850_2 - X_freq_2
Check carry
If clear, then freq_3,2 < AD_3,2. Done
Else, Carry set, so
are the bytes equal?
Nope, so freq_2 > AD_2. Done
Else, those bytes were the same
Point to X_freq_1
W <- X_freq_1
W = AD9850_1 - X_freq_1
Check the Carry status
If C clear, then freq_3..1 < AD_3..1
Else, carry is set.
Are the bytes equal?
No, so X_freq is greater than AD9850. Done
Else, the previous pairs were equal
Point to X_freq_0
W <- X_freq_0
W = AD9850_0 - X_freq_0
If Carry is clear, then
X_freq_3..0 < AD9850_3..0
Else, a set Carry flag indicates that
X_freq_2..0 >= AD9850_3..0
Return with Carry flag indicating
the results of the comparison

;******************************************************************************
;
;
Function: copy_dds_word
;
;
Purpose: Copies the AD0950_3..0 bytes to the frequency registers
;
pointed to by FSR
;
;
Inputs: FSR points to X_freq_0 destination
;
AD9850_3..0 contain the current DDS word to be copied
;
;
Outputs: X_freq_3..0 contain AD9850_3..0 data
;

;
Revisions:
;
Rev 1.0: New function. 4-22-04 W3CD
;
;*******************************************************************************
copy_dds_word
movwf
movf
movwf
incf
movf
movwf
incf
movf
movwf
incf
movf
movwf
return

FSR
AD9850_0,w
INDF
FSR,f
AD9850_1,w
INDF
FSR,f
AD9850_2,w
INDF
FSR,f
AD9850_3,w
INDF

;
;
;
;
;
;

Put pointer into FSR for indirect addr


Get LSB
Store in frequency register
bump pointer
Get next byte
Store it

; Get next byte


; Store it
; Get MSB
; Store it
; Back to caller

;******************************************************************************
;
;
Function: step
;
;
Purpose: Adjusts the frequency by examining the last_dir value and either
;
adds or subtracts the value of fstep_3..0 from the current
;
frequency setting.
;
;
Inputs: last_dir
;
;
Outputs: Adjusts the operating frequency up or down, based on last_dir.
;
the operating frequency is specified by freq_pointer, which
;
is implicitly used here.
;
;
Revisions:
;
Rev 1.0: Copied from PICELGen 2.0 and adapted for use here
;
4-21-04 W3CD
;
;*******************************************************************************
step
btfsc
goto

;
;
;
last_dir,DIR_BIT ;
up
;

call
goto

sub_step
update

; Else, shaft direction is CCW


; Subtract fstep from freq
; Update LCD and DDS

call
call

add_step
check_add

; Add fstep to freq


; Make sure we did not exceed the maximum

call
call

show_freq
update_dds

; Display the new frequency on the LCD


; Send the new control word to the DDS chip

down

Based on the knob direction, either add or


subtract the increment, then update the
LCD and DDS.
Is the knob going up?
Yes, then add the increment

up

update

return

; Back to caller.

; *****************************************************************************
;
;
Function: add_step
;
;
Purpose: This routine adds the 32 bit value of fstep to the 32 bit
;
value in freq. When incrementing, the fstep value is a
;
positive integer. When decrementing, fstep is the complement
;
of the value being subtracted.
;
;
Input: The 32 bit values in fstep and the value pointed to by
;
freq_pointer
;
;
Output: The sum of fstep and the value pointed to by freq_pointer
;
is stored in freq. When incrementing this value may exceed
;
the maximum. When decrementing, it may go negative.
;
;
Revisions:
;
Rev 1.0: Copied from PICELGen Rev 2.0. Modified for use with
;
any frequency variable 4-21-04 W3CD.
;
; *****************************************************************************
add_step
clrf

temp

movf
movwf

freq_pointer,w
FSR

movf
addwf

fstep_0,w
INDF,f

movlw
movwf
call
decf
decf

3
count
ripple_carry
FSR,f
FSR,f

movf
addwf
movlw
movwf
call
decf

fstep_1,w
INDF,f
2
count
ripple_carry
FSR,f

movf
addwf
incf
btfss
goto
incf
add_step1
movf
addwf
return

fstep_2,w
INDF,f
FSR,f
STATUS,C
add_step1
INDF,f
fstep_3,w
INDF,f

; Clear the temp variable


;
; Get the pointer to X_freq_0
; Put this into FSR for indirect addressing
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;

Get low byte of the increment


Add it to the low byte of freq
There may be a carry out of this add...
Propagate ripple carries over next 3 regs
This function handles it
Adjust FSR to point to
X_freq_1
Get the next increment byte
Add it to the next higher byte
Propagate any ripple carries into
the next two registers
Point to X_freq_2
Get the next to most significant increment
Add it to the freq byte
Point to X_freq_3
Any carry?
No, add last byte
Ripple carry up to the highest byte

; Get the most significant increment byte


; Add it to the most significant freq
; Return to the caller

; *****************************************************************************
;
;
Function: ripple_carry
;
;
Purpose: This function propagates carries that result from addition
;
into the higher order bytes of the frequency registers
;
;
Input: FSR points to the register which was just added,
;
temp is cleared by the caller
;
count is set to the number of higher order bytes to process
;
;
Output: Any carry resulting from an addition is propagated into the
;
number of higher order bytes specified by count
;
;
Revisions:
;
Rev 1.0: Copied from PICELGen 2.0. 4-20-04 W3CD
;
; *****************************************************************************
ripple_carry
rlf
incf
addwf
decfsz
goto
return

temp,w
FSR,f
INDF,f
count,f
ripple_carry

;
;
;
;
;
;

Put carry from previous add into temp LSB


Point to next freq reg.
Ripple any carry into next freq reg
Decrement loop count
Loop until all registers are accounted for
Back to caller

; *****************************************************************************
;
;
Function: check_add
;
;
Purpose: Check if freq exceeds the upper limit.
;
;
Input:
vfo_select, vfo_a and vfo_b registers
;
;
Output: If freq is below the limit, it is unchanged. Otherwise, it
;
is set to equal the upper limit.
;
;
Revisions:
;
Rev 1.0: Copied from PICELGen 2.0 and modified for use with
;
any frequency variable. 4-21-04 W3CD
;
; *****************************************************************************
check_add
movlw
addwf
movwf
;
;
;

TABLE_ADJUST
freq_pointer,w
FSR

;
;
;
;

First, get the address of X_freq_3


Offset to 4th element
Add in the pointer to X_freq_0
Put this into FSR for indirect addressing

Check the most significant byte.


movlw
subwf
btfss
goto
btfss
goto

limit_3
INDF,w
STATUS,C
check_exit
STATUS,Z
set_max

;
;
;
;
;
;

Get high limit value


Subtract the limit value
Are we at the limit for the byte?
No, below. Checks are done.
Are the bytes equal?
No, so X_freq_3 > limit_3.

;
;
;

Check the second most significant byte when MSB equals limit_3
decf
movlw
subwf
btfss
goto
btfss
goto

;
;
;

;
;
;
;
;
;
;

Point to 2nd MSB


Second limit byte
Subtract limit value
Are we at the limit for the byte?
No, below. Checks are done.
Might they be equal?
Nope, so X_freq_2 > limit_2

Check the third most significant byte.


decf
movlw
subwf
btfss
goto
btfss
goto

;
;
;

FSR,f
limit_2
INDF,w
STATUS,C
check_exit
STATUS,Z
set_max

FSR,f
limit_1
INDF,w
STATUS,C
check_exit
STATUS,Z
set_max

;
;
;
;
;
;
;

Point to 3rd MSB


Third limit byte
Subtract limit value
Are we at the limit for the byte?
No, below. Checks are done.
Check if the bytes are equal
No, so X_freq_1 > limit_1

Check the least significant byte.


decf
movlw
subwf
btfss
goto

FSR,f
limit_0
INDF,w
STATUS,C
check_exit

;
;
;
;
;
;

Point to LSB
Fourth limit byte
Subtract limit value
Are we at the limit for the byte?
No, below. Checks are done.
Else, it's freq is over limit

movf
movwf

freq_pointer,w
FSR

Get pointer to X_freq_0


into indirect addressing reg.

movlw
movwf

limit_0
INDF

;
;
;
;
;

incf
movlw
movwf

FSR,f
limit_1
INDF

; Point to X_freq_1
; Get next limit byte
; Store it in X_freq_1

incf
movlw
movwf

FSR,f
limit_2
INDF

; Point to X_freq_2
; Get the next limit byte
; Store it in X_freq_2

incf
movlw
movwf

FSR,f
limit_3
INDF

; Point to X_freq_3
; Get the high byte of limit
; Store it in X_freq_3

set_max

check_exit
return

Get low byte of limit


Store in X_freq_0

; Return to the caller

; *****************************************************************************
;
;
Function: sub_step
;
;
Purpose: Subtract the increment step from freq, checking that it does
;
not go below zero.
;
;
Input:
The values in fstep and freq.

;
;
Output: The updated value in freq.
;
;
Revisions:
;
Rev 1.0: Copied from PICELGen 2.0 4-20-04 W3CD
;
; *****************************************************************************
sub_step
call
call

invert_fstep
add_step

; Invert fstep_3..0 to perform the subtraction


; Add the complement to do the subtraction

movlw
addwf
movwf

TABLE_ADJUST
freq_pointer,w
FSR

;
;
;
;

btfss
goto

INDF,MSB
sub_step_exit

; Is high order frequency byte "negative"?


; No, keep going
; Else, set it to zero.

clrf
decf
clrf
decf
clrf
decf
clrf

INDF
FSR,f
INDF
FSR,f
INDF
FSR,f
INDF

;
;
;
;
;
;
;

Determine which VFO is active


Get offset to 4th element
Get address of X_freq_3
Put this into FSR for indirect addressing

set_min

sub_step_exit
call
invert_fstep
return

Clear X_freq_3
Next byte
Clear X_freq_2
Next byte
Clear X_freq_1
Last byte
Clear X_freq_0

; Restore the original fstep value


; Return to the caller

;*****************************************************************************
;
;
Function: invert_fstep
;
;
Purpose: Support function for sub_step and calibrate. This function
;
negates the value of fstep_3..0 to assist in the frequency
;
decrement. This operation is performed twice by sub_step, and
;
is also used by calibrate.
;
;
Input:
fstep_3, fstep_2, fstep_1, fstep_0
;
;
Output:
fstep_3..0 contain the 2's complement of their original value
;
;
Revisions:
;
Rev 1.0 Copied from PICELGen 2.0 4-20-04 W3CD
;
; *****************************************************************************
invert_fstep
comf
comf
comf
comf
incfsz
goto

fstep_0,f
fstep_1,f
fstep_2,f
fstep_3,f
fstep_0,f
invert_done

;
;
;
;
;
;
;

Invert the bits in


fstep_0
fstep_1
fstep_2
fstep_3
Now incremnt fstep_0 to get 2's complement
If fstep_0 > 0, then done

incfsz fstep_1,f
goto
invert_done
incfsz fstep_2,f
goto
invert_done
incf

fstep_3,f

invert_done
return

;
;
;
;
;
;
;
;

Else, there was a carry out of fstep_0


Add 1 to fstep_1
If fstep_1 > 0, then done
Else, there was a carry out of fstep_1
Add 1 to fstep_2
if fstep_2 > 0, then done
Else, there was a carry out of fstep_2
Increment the high byte

; Back to caller

;*****************************************************************************
;
; Function: poll_encoder
;
; Purpose: This routine does the following:
;
1. Reads the encoder bits until a change is detected, then
;
determines the direction the knob was moved.
;
2. Performs PIC-EL mechanical shaft encoder debounce and
;
detent processing is so enabled.
;
3. Reads PB_2 and determines if a band change is
;
requested.
;
;
Input: Knob input read from port A
;
PB_2
;
ren_old -> the last encoder bits read
;
last_dir -> the last direction moved
;
;
Output: ren_read: The contents of PORTA when the encoder was moved
;
ren_new: The current encoder bits
;
last_dir: The last direction (0 = down, 2 = up [AKA UP_BIT])
;
;
Revisions:
;
;
Ver 1.0: Copied from PICELGen 2.0 and modified for use here
;
4-20-04 W3CD
;
;*****************************************************************************
poll_encoder
call
btfsc
goto
movf
movwf

process_pb
PB_flags,F_SET
pe_exit

; Check if there is pushbutton activity


; If the F_SET bit is set, then done
;

PORTA,w
ren_read

; Get the current encoder value


; Save it

IFDEF DETENT_ENCODER
call
wait_1ms
movf
PORTA,w
xorwf ren_read,w
btfss STATUS,Z
goto
poll_encoder
ENDIF
movlw
andwf
movwf

ENCODER_BITS
ren_read,w
ren_new

;
;
;
;
;

debounce time
read the port again
Compare with previous value
Are they equal?
Poll again if not

; Get encoder mask (to isolate RA0 and RA1)


; Isolated encoder bits into W
; Save new value

xorwf
btfsc
goto

ren_old,w
STATUS,Z
poll_encoder

;
;
;
;

Has it changed?
Check zero-flag (zero if no change)
No change, keep looking until it changes
Else, Zero-flag is not set, so continue on

; It changed. Now determine which direction the encoder turned.


;=============================================================================
; Encoder bits are on RA0 and RA1 - the two low order bits of ren_new
; A and B are "gray code" - 90 degrees out of phase (quadrature)
;
___
___
;
| | | |
; A ____| |___| |___
;
___
___
;
| | | |
; B
___| |___| |___
;
^ ^ ^ ^ ^ ^ ^ ^
;
a b c d a b c d
;
;
A B
; At point a: 0 0
; At point b: 1 0
; At point c: 1 1
; At point d: 0 1
;
; Going UP, the sequence is a,b,c,d,a,b,c,d, etc. so the sequence is:
;
00, 10, 11, 01, 00, 10, 11, 01, etc.
;
; Going DOWN, the sequence is d,c,b,a,d,c,b,a, etc. so the sequence is:
;
01, 11, 10, 00, 01, 11, 10, 00, etc.
;
; To determine if the sequence is UP or DOWN:
; 1) Take the "Right-Bit" of any pair.
; 2) XOR it with the "Left-Bit" of the next pair in the sequence.
; 3) If the result is 1 it is UP
;
If the result is 0 it is DOWN
;
; The direction flag is 0 (DOWN) or 2 (UP) because of bit positioning
;=============================================================================
bcf
STATUS,C
; Clear the carry bit to prepare for rotate
rlf
ren_old,f
; Rotate old bits left to align "Right-Bit"
movf
ren_new,w
; Set up new bits in W
xorwf ren_old,f
; XOR old (left shifted) with new bits
movf
ren_old,w
; Put XOR results into W also
andlw UP_BIT
; Mask to look at only "Left-Bit" of pair
movwf next_dir
; Save result (in W) as direction (bit=UP)
xorwf last_dir,w
; See if direction is same as before
;
;
Prevent encoder slip from giving a false change in direction.
;
IFNDEF DETENT_ENCODER
btfsc STATUS,Z
goto
pe_continue
movf
next_dir,w
movwf last_dir
movf
ren_new,w
movwf ren_old
goto
poll_encoder
ENDIF

;
;
;
;
;
;
;

Zero flag set? (i.e, is direction same?)


Yes, same direction so no slip; keep going
No Zero-flag, so direction changed
Update the direction indicator
Save the current encoder bits (now in W)
for next time
Try again

pe_continue
clrf
btfsc
goto

last_dir
ren_old,DIR_BIT
pe_up

IFDEF DETENT_ENCODER
movf
ren_new,w
movwf ren_old
decf
enc_counter,f
btfsc enc_counter,0
goto
poll_encoder
btfsc enc_counter,1
goto
poll_encoder

;
;
;
;

Clear last_dir (default is DN)


Are we going UP?
Yes, go process it.
Else, we are goiong down

;
;
;
;
;
;
;
;

Get the current encoder bits


Save them in ren_old for the next time
decrement the inter-detent counter
Check if bit 0 is cleared
If not, then poll some more
Else, check if bit 1 is cleared
If not, then poll some more
Else, assume the encoder is on a detent

ENDIF
goto

pe_movement

; Indicate that the encoder has moved

movlw
movwf

UP_BIT
last_dir

; Get UP value
; and set in last_dir

pe_up

IFDEF DETENT_ENCODER
movf
ren_new,w
movwf ren_old
incf
enc_counter,f
btfsc enc_counter,0
goto
poll_encoder
btfsc enc_counter,1
goto
poll_encoder
ENDIF
pe_movement
movf
movwf

;
;
;
;
;

Get the current encoder bits


Save them in ren_old for the next time
Increment the encoder counter
Return only on modulo 4 counts

; Loop again if not modulo 4


; Arrive here when encoder is being turned

ren_new,w
ren_old

; Get the current encoder bits


; Save them in ren_old for the next time
;

pe_exit
return

; Return to the caller

;****************************************************************************
;
;
Function: process_pb
;
;
Purpose: This function examines the state of PB_1 and PB_2
;
and has mulitple operating characteristics.
;
;
1. Pressing and releasing PB1 alone moves the cursor left
;
one position and increments the decade to be adjusted
;
by the shaft encoder. The cursor and frequency decade
;
wrap around the 10 MHz position to the 1 Hz position.
;
;
2. Pressing and holding PB1 for more than 1 second starts
;
the auto-repeat function and the decade are updated
;
continuously until PB1 is released. The update rate
;
is 4 times/sec.
;
;
3. Pressing PB2 alone sets the F_SET flag. This tells
;
higher order functions that the selected frequency

;
has been accepted, or in the case of active sweeping,
;
causes the sweep to terminate.
;
;
4. Pressing PB2 while PB1 is held down moves the cursor
;
to the right and the frequency decade is decreased
;
accordingly.
;
;
5. Holding PB1 and PB2 down for more than 1 second enables
;
the auto-repeat function and the cursor moves to the
;
right at a rate of 4 times/sec.
;
;
;
Inputs:
PORTA bits PB_1 and PB_2 and PB_flags
;
;
Outputs: If a button is pressed, then various control variables are
;
updated. The function exits if no buttons are pressed.
;
;
Revisions:
;
Ver 1.0: Copied from PICELGen 2.0 and modified extensively for
;
use here. 4-20-04 W3CD
;
;****************************************************************************
process_pb
btfsc
goto
btfsc
goto
btfss
goto
process_pb_1
movlw
movwf
bcf

PB_flags,PB_CS_FLG ; Check if in calibrate mode


process_pb_exit
; If bit is set, skip processing
; Else, examine the button status
PORTA,PB_1
; Is PB_1 being pressed?
pb_2_check
; Check PB_2 if not.
; Else, there's further processing to do
PORTA,PB_2
; Check PB_2 to see if both are pressed
process_both
; If it's active, process both
; Arrive here to process just PB_1
PB_RPT_WAIT
; Setup the repeat delay timer
PB_wait_count
;
PB_flags,PB_RPT_FLG ; Clear repeat flag

pb_1_release_wait
call
wait_32ms
btfsc PORTA,PB_1
goto
pb_1_released
btfss
goto

PORTA,PB_2
process_both

decf
btfss
goto

PB_wait_count,f
STATUS,Z
pb_1_release_wait

movlw
call

DECADE_INCREMENT
update_decade

movlw
movwf

PB_RPT_DLY
PB_wait_count

bsf
bcf
goto

; Wait a bit before checking again


; Is PB_1 still behing held?
; If it's released, adjust decade and exit
;
;
;
;
;
;
;
;
;

;
;
;
PB_flags,PB_RPT_FLG ;
PB_flags,PB_2_PRESS ;
pb_1_release_wait ;

Else, check for a pressing of PB_2


If PB_2 was pressed, then process both
Else, test if repeat delay has expired
Decrement wait period
Check the Zero bit
Keep looping until something happens
Else, repeat delay has expired.
Increase decade adjustment
Bump the decade-related variables
Load the repeat interval value
into PB_wait_count
Set the repeat flag to prevent last
update when PB_1 is released
Clear the PB_2 flag, just in case
Loop some more

pb_1_released
movlw
btfsc
goto
goto

DECADE_INCREMENT
PB_flags,PB_2_PRESS
process_pb_exit
process_pb_release

;
;
;
;
;

Arrive here when PB1 was released


Increase decade adjustment
Check if PB2 was pressed before repeating
If PB2 was pressed, skip update
common code for pushbuttons

;******************************************************************************
;
;
Arrive here when PB_1 was not pressed. Check for PB_2 activity, which
; indicates that the displayed frequency has been accepted. If PB_1 is pressed
; during the PB_2 release wait, handle cursor right movement.
;
;******************************************************************************
pb_2_check
btfsc
goto

PORTA,PB_2
process_pb_exit

pb_2_release_wait
call
wait_32ms
btfsc PORTA,PB_2
goto
pb_2_released
btfsc
goto
btfss
goto
goto
pb_2_released
bsf
goto

; Is PB_2 being pressed?


; If not, then exit
; Else, process PB_2 pressing
; Wait a bit before checking again
; Is PB_2 still behing held?
; If it's released, then set flag

PB_flags,PB_CS_FLG ; Check if sweep is active


pb_2_release_wait ; If so, avoid checking PB 1
; Else, not in sweep mode
PORTA,PB_1
; Else, check for a late pressing of PB_1
process_both
; If PB_1 was pressed, then process both
pb_2_release_wait ; Else, continue waiting.
PB_flags,F_SET
process_pb_exit

; Set the flag that freq was selected


; or end of sweep was requested.
; Exit

;************************************************************************
;
;
Arrive here when both PB_1 and PB_2 were pressed.
;
;************************************************************************
process_both
movlw
movwf
bcf
bsf

PB_RPT_WAIT
PB_wait_count
PB_flags,PB_RPT_FLG
PB_flags,PB_2_PRESS

; Setup the repeat delay timer


;
; Clear repeat flag
; Set this flag for PB1 handling

both_release_wait
call
btfsc
goto
btfsc
goto

wait_32ms
PORTA,PB_1
pb_both_done
PORTA,PB_2
pb_both_done

decf
btfss
goto

PB_wait_count,f
STATUS,Z
both_release_wait

;
;
;
;
;
;
;
;
;
;

Wait for the PBs to be released


Use our favorite delay
If PB_1 was released,
Then done
Check PB_2 for same condition
Done if operator released it
Else, both buttons are still held
Decrement wait period
Check if delay is done.
If not, loop

movlw
call

DECADE_DECREMENT
update_decade

; Else, timeout period expired.


; Decrease decade adjustment
; Bump the decade-related variables

movlw
movwf
bsf
goto

PB_RPT_DLY
PB_wait_count
PB_flags,PB_RPT_FLG
both_release_wait

;
;
;
;

DECADE_DECREMENT

; Either PB_1 or PB_2, or both, released


; Load up decade adjustment
; And continue with common PB code

pb_both_done
movlw

Load the repeat interval value


into PB_wait_count
Indicate that auto-repeat was active
Loop some more

;************************************************************************
;
;
Common code to handle the release of either PB_1 or PB_2 in the
;
decade adjust mode. Wreg contains the decade adjustment value.
;
;************************************************************************
process_pb_release
btfsc
goto

PB_flags,PB_RPT_FLG ; If repeat flag was set,


process_pb_exit
; don't do the final adjustment.
;
call
update_decade
; Else, adjust decade of interest
btfss PORTA,PB_1
; Test if PB_1 is still held
goto
process_pb
; If so, loop, don't exit
; Else, we're really done
process_pb_exit
; Exit point
bcf
PB_flags,PB_2_PRESS ; Clear PB2 flag, since PB1 is released
bcf
PB_flags,PB_RPT_FLG ; Clear the common repeat flag and
return
; Return to caller
;****************************************************************************
;
;
Function: update_decade
;
;
Purpose: Support function for PB_1_check. Adjusts the cursor position
;
and decade table pointer. It also calls get_decade to update
;
the values of fstep_3 through fstep_0. Lastly, it calls
;
show_freq to update the cursor position on the LCD.
;
;
Inputs: W contains the decade adjustment.
;
;
Outputs: cur_pos is modified, modulo 8 (0 to 7)
;
decade is modified
;
fsetp_3..0 are modified in the sequence of:
;
1Hz, 10Hz, 100Hz, 1KHz, 10KHz, 100KHz, 1MHz, 10 MHz, 1Hz, etc.,
;
depending on direction.
;
;
Revisions:
;
Ver 1.0 Copied from PICELGen 2.0: 4-20-04 by W3CD.
;
;****************************************************************************
update_decade

addwf
andlw

decade,w
DECADE_MASK

movwf
call
movwf
call
call
return

decade
cursor_table
cur_pos
get_decade
show_freq

;
;
;
;
;
;
;
;
;

Add the decade adjustment


Only using 8 decades here and the
decades wrap around the 10MHz position
Store updated decade
Get the translated cursor position
Save the updated cursor value.
Update fstep_3..0
update display to show new cursor position
Return to the caller

;****************************************************************************
;
;
Function: get_decade
;
;
Purpose: Support function which sets new increment values for
;
fstep_3 through fstep_0 using values in decade_table
;
;
Inputs:
decade
;
;
Outputs: fstep_3, fstep_2, fstep_1 and fstep_0 are updated
;
;
Revisions:
;
Ver 1.0 Copied from PICELGen 2.0 4-20-04 W3CD
;
;****************************************************************************
get_decade
movlw
movwf
movlw
movwf
movf
movwf
bcf
rlf
rlf
get_decade_loop
movf
call
movwf
decf
incf
decfsz
goto
return

FREQ_COUNT
count
fstep_3
FSR
decade,w
temp
STATUS,C
temp,f
temp,f

;
;
;
;
;
;
;
;
;

There are four bytes to copy


Set up count variable
Get target address
Store in pointer register
Get the decade index
Copy it to temp variable
Make sure Carry bit is clear
Shift decade index 2 place to left
to develop table index

temp,w
decade_table
INDF
FSR,f
temp,f
count,f
get_decade_loop

;
;
;
;
;
;
;
;
;

Get the decade pointer into W


Get a decade byte
Store it
Point to next lowest fstep byte
Increment decade table pointer
Decrement loop count
loop while more bytes remain
Exit when done

; *****************************************************************************
;
;
Function: calibrate
;
;
Purpose: This routine is entered at start up if the calibrate
;
push button is (PIC-EL PB_1) is pressed at power-on time.
;
;
If a 8-character LCD is used,"10000000" is displayed
;
If a 16-character LCD is used," 10,000.000 CAL " is displayed

;
;
The DDS chip is programmed to produce 10 MHz, based on the
;
osc value stored in the EEPROM. As long as the button is
;
pressed, the osc value is slowly altered to allow the output
;
to be trimmed to exactly 10 MHz. Once the encoder is turned
;
after the button is released, the new osc value is stored in
;
the EEPROM and normal operation begins.
;
;
Input:
The original osc constant in EEPROM
;
;
Output: The corrected osc constant in EEPROM
;
;
Revisions:
;
Rev 1.0: Copied from PICELGen 2.0 and replaced code that added
;
fstep to frequency with a call to add_step. This saves
;
program space. 4-22-04 W3CD.
;
; *****************************************************************************
calibrate
bsf

PB_flags,PB_CS_FLG ; Set the cal bit to prevent cursor movement

movlw
movwf
movlw
call

start_freq_0
FSR
EEcal_freq
read_EEPROM_freq

;
;
;
;

Get pointer to working frequency registers


Save for indiect addressing
Point to calibration frequency in EEPROM
Read the freq (10.000000 MHz)

movlw
movwf
call

start_freq_0
freq_pointer
show_freq

; Get pointer to the working freq registers


; Store it for use ahead
; The starting reference oscillator values h

ave
; already been read from EEPROM. Convert ref
erence
; freq and display it.
IF LCDCHAR == 16
IF LCD16_LINEAR == 0
movlw PICEL_LONG_9 + 4

; Point LCD at position 13 for AmQRP 16x1 LC

D
ELSE
movlw

CMD_MOV_CUR_DISP+12 ; Short jump to LCD position 13

call
movlw
call

cmnd2LCD
cal_msg
display_message

;
;
;
;

call
call

update_dds
poll_encoder

; Calc new data and update the DDS chip


; Wait until the encoder has moved.

btfsc
goto

PORTA,PB_1
cal_done

clrf
clrf
clrf

fstep_3
fstep_2
fstep_1

movlw

SMALL_FSTEP

;
;
;
;
;
;
;
;

ENDIF

ENDIF

Position the cursor


Display "CAL" on LCD
(position 16 is blank)
16 Character LCDs

cal_loop

Check if PB1 was pressed


If released, then exit calibrate mode
Else, stay in cal and update osc.
Clear the three most significant
bytes of fstep
Now determine which increment to use
Assume that we are adjusting slowly

btfss
movlw

ren_read,PB_2
LARGE_FSTEP

movwf
btfsc
goto

fstep_0
last_dir,DIR_BIT
faster

call

invert_fstep

;
;
;
;
;
;
;
;

Check if PB2 was pressed


If so, then use the large increment
Update fstep_0
Are we moving down?
No, increase the osc value
Else, decreasing the osc value
Use this handy function to complement fste

p
; and continue
faster
movlw
movwf
call

osc_0
freq_pointer
add_step

; Point to frequency operand


; Store in pointer
; osc_3..0 = osc_3..0 + f_step3..0

movlw
movwf
goto

start_freq_0
freq_pointer
cal_loop

; Restore pointer to the


; working freq registers
; Back to top of loop

cal_done
movlw
movwf
clrw
call
bcf
return

;
;
;
;
write_EEPROM_freq ;
PB_flags,PB_CS_FLG ;
;
osc_0
FSR

Arrive here when PB1 was released


Get starting address of osc values
Store in pointer
Point to start of EEPROM.
Write contents to EEPROM.
Clear the calibrate flag
Return to the caller

;*****************************************************************************
;
;
Function: calc_dds_word
;
;
Purpose: Multiply the 32 bit number for oscillator frequency times the
;
32 bit number for the displayed frequency.
;
;
Input:
The reference oscillator value in osc_3 ... osc_0 and the
;
current frequency stored in freq_3 ... freq_0. The reference
;
oscillator value is treated as a fixed point real, with a 24
;
bit mantissa.
;
;
Output: The result is stored in AD9850_3 ... AD9850_0.
;
;
Revision:
;
Ver 1.0: Copied from PICELGen 2.0 4-20-04 W3CD
;
; *****************************************************************************
calc_dds_word
clrf
clrf
clrf
clrf
clrf
movlw
movwf
movf
movwf
movf
movwf
movf

AD9850_0
AD9850_1
AD9850_2
AD9850_3
AD9850_4
0x20
count
osc_0,w
temp_freq_0
osc_1,w
temp_freq_1
osc_2,w

;
;
;
;
;
;
;
;
;
;
;
;

Clear the AD9850 control word bytes

Set count to 32 (4 osc bytes of 8 bits)


Keep running count
Move the four osc bytes
to temporary storage for this multiply
(Don't disturb original osc bytes)

movwf
movf
movwf
movf
mult_loop
movwf
ddr.
bcf
btfss
goto
movf
addwf
btfss
goto
incfsz
goto
incfsz
goto
incf
add7
incf
movf
addwf
btfss
goto
incfsz
goto
incf
add8
incf
movf
addwf
btfss
goto
incf
add9
incf
movf
addwf
noAdd
rrf
rrf
rrf
rrf
rrf
rrf
rrf
rrf
rrf

temp_freq_2
osc_3,w
temp_freq_3
freq_pointer,w

;
;
;
; Now develop the active vfo frequency pointer
; Get pointer to X_freq_0

FSR

; Store X_freq_0 pointer in FSR for indirect a

STATUS,C
temp_freq_0,0
noAdd
INDF,w
AD9850_1,f
STATUS,C
add7
AD9850_2,f
add7
AD9850_3,f
add7
AD9850_4,f

;
;
;
;
;
;
;
;
;
;
;
;

Start with Carry clear


Is bit 0 (Least Significant bit) set?
No, don't need to add freq term to total
Yes, get the X_freq_0 term
and add it in to total
Does this addition result in a carry?
No, continue with next freq term
Yes, add one and check for another carry
No, continue with next freq term
Yes, add one and check for another carry
No, continue with next freq term
Yes, add one and continue

FSR,f
INDF,w
AD9850_2,f
STATUS,C
add8
AD9850_3,f
add8
AD9850_4,f

;
;
;
;
;
;
;
;

Point to X_freq_1
Use the X_freq_1 term
Add freq term to total in correct position
Does this addition result in a carry?
No, continue with next freq term
Yes, add one and check for another carry
No, continue with next freq term
Yes, add one and continue

FSR,f
INDF,w
AD9850_3,f
STATUS,C
add9
AD9850_4,f

;
;
;
;
;
;

Point to X_freq_2
Use the freq_2 term
Add freq term to total in correct position
Does this addition result in a carry?
No, continue with next freq term
Yes, add one and continue

FSR,f
INDF,w
AD9850_4,f

; Point to X_freq_3
; Use the X_freq_3 term
; Add freq term to total in correct position

AD9850_4,f
AD9850_3,f
AD9850_2,f
AD9850_1,f
AD9850_0,f
temp_freq_3,f
temp_freq_2,f
temp_freq_1,f
temp_freq_0,f

;
;
;
;
;
;
;
;
;

movf
freq_pointer,w
decfsz count,f
goto
mult_loop
;
clrf
AD9850_4
(un-comment for AD9850)
movlw 0x01
26/2/12 (comment out for AD9850)
movwf AD9850_4
t out for AD9850)

Shift next multiplier bit into position


Rotate bits to right from byte to byte

Shift next multiplicand bit into position


Rotate bits to right from byte to byte

; Get the pointer to X_freq_0


; One more bit has been done. Are we done?
; No, go back to use this bit
; Yes, clear _4. Answer is in bytes _3 .. _0
; Turn on 6x clock multiplier (AD9851)Added TM
; Last byte to be sent Added TM 26/2/12 (commen

return

; Done.

; *****************************************************************************
;
;
Function: update_dds, send_dds_word
;
;
Purpose: This routine sends the AD9850 control word to the DDS chip
;
using a serial data transfer. The first entry point, update_dds,
;
calls calc_dds_word to determine the AD9850 data. The second
;
call is used when the DDS data is already calculated.
;
;
Input:
update_dds: freq_pointer points to the frequency value to be
;
converted to DDS data before sending to DDS.
;
;
send_dds_word: AD9850_4 ... AD9850_0 contain the data to send.
;
;
Output: The DDS chip register is updated.
;
;
Revsisions:
;
Rev 1.0: Copied from PICELGen 2.0 and added call to calc_dds_word
;
to reduce program space, since this happens quite often.
;
4-21-04 W3CD.
;
; *****************************************************************************
update_dds
call
send_dds_word
movlw
movwf
next_byte
movf
movwf
movlw
movwf
next_bit
rrf
btfss
goto
bsf
bsf
bcf
goto
send0
bcf
bsf
bcf
break
decfsz
goto
incf
movlw
subwf
btfss
goto
bsf
bcf
call

calc_dds_word

; calculate the data to send to AD9850

AD9850_0
FSR

; Point FSR at AD9850


;

INDF,w
byte2send
0x08
bit_count

;
;
; Set counter to 8
;

byte2send,f
STATUS,C
send0
PORTB,DDS_dat
PORTB,DDS_clk
PORTB,DDS_clk
break

;
;
;
;
;
;
;

PORTB,DDS_dat
PORTB,DDS_clk
PORTB,DDS_clk

; Send zero
; Toggle write clock
;

bit_count,f
next_bit
FSR,f
AD9850_4+1
FSR,w
STATUS,C
next_byte
PORTB,DDS_load
PORTB,DDS_load

;
;
;
;
;
;
;
;
;

set_leds

Test if next bit is 1 or 0


Was it zero?
Yes, send zero
No, send one
Toggle write clock

Has the whole byte been sent?


No, keep going.
Start the next byte unless finished
Next byte (past the end)

Send load signal to the AD9850

return
;
;
;
;
;
;
;
;
;
;
;
;
;

; Back to caller

*****************************************************************************
*
*
* Purpose: Power on initialization of Liquid Crystal Display. The LCD
*
*
controller chip must be equivalent to an Hitachi 44780. The
*
*
LCD is assumed to be a 8x1 or a 16x1 display.
P
*
*
* Input: None
*
*
*
* Output: None
*
*
*
*****************************************************************************

init_LCD
call
movlw
movwf
bsf
call
bcf
movlw
movwf
bsf
call
bcf
movlw
movwf
bsf
call
bcf
movlw
movwf
bsf
call
bcf
movlw
call
movlw
call
movlw
call
movlw
call

wait_64ms
CMD_RESET_LCD
PORTB
PORTB,LCD_e
wait_64ms
PORTB,LCD_e
CMD_RESET_LCD
PORTB
PORTB,LCD_e
wait_32ms
PORTB,LCD_e
CMD_RESET_LCD
PORTB
PORTB,LCD_e
wait_32ms
PORTB,LCD_e
PICEL_IFC1
PORTB
PORTB,LCD_e
wait_16ms
PORTB,LCD_e
PICEL_IFC2
cmnd2LCD
PICEL_DISPLAY_OFF
cmnd2LCD
CMD_CLEAR_LCD
cmnd2LCD
PICEL_CURSOR_RT
cmnd2LCD

;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;

Wait for LCD to power up


LCD init instruction (First)
Send to LCD via RB3..RB0
Set the LCD E line high,
wait a "long" time,
and then Clear E
LCD init instruction (Second)
Send to LCD via RB3..RB0
Set E high,
wait a while,
and then Clear E
LCD init instruction (Third)
Send to LCD via RB3..RB0
Set E high,
wait a while,
and then Clear E
Set the LCD for 4-bit mode I/O
Send to LCD via RB3..RB0
Set E high,
wait a while,
and then Clear E
Set operational characteristics of LCD
Send command in w to LCD
Display off, cursor and blink off
Send command to LCD
Clear and reset cursor
Send command in w to LCD
Set cursor to move right, no display shift
Send command in w to LCD

movlw PICEL_DISPLAY_ON ; Display on, cursor on, blink off


call
cmnd2LCD
; Send command to LCD
return
; Done!
;****************************************************************************
;
;
Purpose: Displays power-up message and version information.
;
;
Input:
MCODE_REV_0 through MCODE_REV_4 set up
;
;
Output: LCD displays debug info
;

; ****************************************************************************
display_version
movlw
call

sign_on_msg
display_message

; Offset to Sign-on message


; Display it on LCD

IF LCDCHAR == 8
call
wait_a_sec
; Let it sit a while
movlw CMD_MOV_CUR_DISP ; Home the cursor
call
cmnd2LCD
ELSE
IF LCD16_LINEAR == 0
movlw PICEL_LONG_9
call
cmnd2LCD
ENDIF
ENDIF
movlw ver_msg
call
display_message
call
wait_a_sec
return

; Move to 9th character position


; for AmQRP 16x1 LCD
; Move cursor to appropriate position
;
;
;
;

Offset to version message


Display on the LCD
Yet another pause
back to caller

;*******************************************************************************
;
;
Function: display_message
;
;
Purpose: Displays the selected message in message_table on the LCD
;
;
Input:
Wreg contains the offset of the desired message to display
;
;
Output:
Message is displayed on LCD
;
;
Revisions:
;
Rev 1.0: Copied from PICELGen 2.0 4-20-04 W3CD
;
;*******************************************************************************
display_message
movwf message_pointer

; Save the starting message offset

display_loop
call
andlw
btfsc
goto

message_table
FLAG_MASK
STATUS, Z
display_exit

;
;
;
;

Call the table to return a character


Just set status flags, Z to be precise.
If the char is null, then end of message
and exit

data2LCD
message_pointer,f
message_pointer,w
display_loop

;
;
;
;

Else, send the character to the LCD


Increment offset to next character
Get it into W
Loop again

call
incf
movf
goto
display_exit
return

; Exit when the message has been sent

; *****************************************************************************
;
;
Function: bin2BCD
;
;
Purpose: This subroutine converts a 32 bit binary number to a 10 digit
;
BCD number. The input value taken from freq(0 to 3) is
;
preserved. The output is in BCD(0 to 4), each byte holds =>
;
(hi_digit,lo_digit), most significant digits are in BCD_4.
;
This routine is a modified version of one described in
;
MicroChip application note AN526.
;
;
Input:
The value in freq_0 ... freq_3
;
;
Output: The BCD number in BCD_0 ... BCD_4
;
;
Revisions:
;
Rev. 1.0: Copied from PICELGen 2.0 and modified to use any
;
frequency variable 4-21-04 W3CD.
;
; *****************************************************************************
bin2BCD
movlw
movwf
clrf
clrf
clrf
clrf
clrf

0x20
BCD_count
BCD_0
BCD_1
BCD_2
BCD_3
BCD_4

; Set loop counter


; to 32
; Clear output
; "
"
; "
"
; "
"
; "
"

;
; Unable to use indirect addressing here, so copy the active VFO
; data to freq3..0 for use by this function
;
movf freq_pointer,w
movwf FSR

; Get X_freq_0 address


; Copy address to FSR

movf
movwf
incf
movf
movwf
incf
movf
movwf
incf
movf
movwf

;
;
;
;
;
;
;
;
;
;
;
;

INDF,w
temp_freq_0
FSR,f
INDF,w
temp_freq_1
FSR,f
INDF,w
temp_freq_2
FSR,f
INDF,w
temp_freq_3

Get low byte of frequency


store it for conversion
Point to X_freq_1
Get X_freq_1
Store it
Point to X_freq_2
Get the byte
Store it
Point to X_freq_3
Get it
Store it
Now ready for conversion

bin_loop
bcf

STATUS,C

; Clear carry bit in STATUS

;
; Rotate bits in freq bytes. Move from LS
; Likewise, move from freq_1 to freq_2 and
;
rlf
temp_freq_0,f
; Rotate
rlf
temp_freq_1,f
; Rotate
rlf
temp_freq_2,f
; Rotate

byte (freq_0) to next byte (freq_1).


from freq_2 to freq_3.
left, 0 -> LS bit, MS bit -> Carry
left, Carry->LS bit, MS bit->Carry
left, Carry->LS bit, MS bit->Carry

rlf
btfsc
bsf

temp_freq_3,f
STATUS,C
temp_freq_0,0

; Rotate left, Carry->LS bit, MS bit->Carry


; Is Carry clear? If so, skip next instruction
; Carry is set so wrap and set bit 0 in freq_0

;
; Build BCD bytes. Move into LS bit of BCD bytes (LS of BCD_0) from MS bit of
; freq_3 via the Carry bit.
;
rlf
BCD_0,f
; Rotate left, Carry->LS bit, MS bit->Carry
rlf
BCD_1,f
; Rotate left, Carry->LS bit, MS bit->Carry
rlf
BCD_2,f
; Rotate left, Carry->LS bit, MS bit->Carry
rlf
BCD_3,f
; Rotate left, Carry->LS bit, MS bit->Carry
rlf
BCD_4,f
; Rotate left, Carry->LS bit, MS bit->Carry
decf
BCD_count,f
; Decrement loop count
btfss STATUS,Z
; Is loop count now zero?
goto
adjust
; No, go to adjust
return
; Yes, EXIT
; ============================================================================
; Internal subroutine, called by bin2BCD main loop only
;
; As BCD bytes are being built, make sure the nibbles do not grow larger than 9.
; If a nibble gets larger than 9, increment to next higher nibble.
; (If the LS nibble of a byte overflows, increment the MS nibble of that byte.)
; (If the MS nibble of a byte overflows, increment the LS nibble of next byte.)
;
adjust
movlw BCD_0
; Get pointer to BCD_0
movwf FSR
; Put pointer in FSR for indirect addressing
call
adj_BCD
;
incf
FSR,f
; Move indirect addressing pointer to BCD_1
call
adj_BCD
;
incf
FSR,f
; Move indirect addressing pointer to BCD_2
call
adj_BCD
;
incf
FSR,f
; Move indirect addressing pointer to BCD_3
call
adj_BCD
;
incf
FSR,f
; Move indirect addressing pointer to BCD_4
call
adj_BCD
;
goto
bin_loop
; Back to main loop of bin2BCD
; ============================================================================
adj_BCD ; Internal subroutine, called by adjust only
movlw 3
; Add 3
addwf INDF,w
; to LS digit
movwf BCD_temp
; Save in temp
btfsc BCD_temp,3
; Is LS digit + 3 > 7 (Bit 3 set)
movwf INDF
; Yes, save incremented value as LS digit
movlw 0x30
; Add 3
addwf INDF,w
; to MS digit
movwf BCD_temp
; Save as temp
btfsc BCD_temp,7
; Is MS digit + 3 > 7 (Bit 7 set)
movwf INDF
; Yes, save incremented value as MS digit
return
; Return to adjust subroutine
; *****************************************************************************
;
;
Function: show_freq
;
;
Purpose: Display the frequency setting on the LCD.
;
If a 1x8 LCD display so display freq in Hz - e.g. 14025000
;
If a 1x16 LCD display so display freq kHz - e.g 14,025.000 kHz

;
;
Input:
The values in BCD_4 ... BCD_0
;
;
Output: The number displayed on the LCD
;
;
Revisions:
;
Rev 1.0 Copied from PICELGen 2.0 and modified to reduce program
;
space by substituting repetitive operations with calls
;
to send_digit. 4-21-04 W3CD
;
; *****************************************************************************
show_freq
call
movlw
call

bin2BCD
; Convert the binary freq. to BCD for display
CMD_HOME_CUR_LCD ; Point the LCD to first LCD digit location
cmnd2LCD
; Send starting digit location to LCD

IF LCDCHAR == 16
movlw ' '
call
data2LCD
ENDIF
swapf
call
movf
call

BCD_3,w
send_digit
BCD_3,w
send_digit

IF LCDCHAR == 16
movlw ','
call
data2LCD
ENDIF
swapf
call
movf
call
swapf
call

BCD_2,w
send_digit
BCD_2,w
send_digit
BCD_1,w
send_digit

IF LCDCHAR == 16
movlw '.'
call
data2LCD
IF LCD16_LINEAR == 0
movlw PICEL_LONG_9
call
cmnd2LCD
ENDIF
ENDIF
movf
call
swapf
call
movf
call

BCD_1,w
send_digit
BCD_0,w
send_digit
BCD_0,w
send_digit

IF LCDCHAR == 16
movlw khz_msg

; Send a space
; to position 1 of LCD
;
;
;
;
;
;

There are 8 digits to send, and these are


packed into BCD_3 thru BCD_0.
Put 10MHz BCD digit into lower nybble of W
Send the 10 MHz digit
Put 1MHz BCD digit into lower nybble of W
Send the 1MHz digit

; Get a comma
; Send it to LCD
;
;
;
;
;
;

Put 100KHz BCD digit into lower nybble of W


Send that digit to LCD
Put 10KHz BCD digit into lower nybble of W
Send that digit to LCD
Put 1KHz BCD digit into lower nybble of W
Send byte in W to LCD

; Get a period
; Send it to LCD
; If using the AmQRP 16x1 LCD,
; point to LCD digit number nine
; Send command byte in W to LCD

;
;
;
;
;
;

Put 100 Hz BCD digit into lower nybble of W


Send it to LCD
Get 10 Hz BCD digit into lower nybble of W
Send it to LCD
Put 1 Hz BCD digit into lower nybble of W
Send to LCD

; Display "KHz" on LCD

call

display_message

; Position 16 is blank

call

set_cursor_pos

; Indicate the lowest digit position


; being modified. - W3CD 3-26-04
; Establish led states
;

ENDIF

call
set_leds
return

; *****************************************************************************
;
;
Function: send_digit
;
;
Purpose: Formats a BCD digit into ASCII and sends it to the LCD.
;
This is a support function for show_freq.
;
;
Input:
The low nybble of W contains the BCD digit to send
;
;
Output: The formatted digit is send to the LCD
;
;
Revisions:
;
;
Rev 1.0 Added function to reduce program size 4-21-04 W3CD.
;
; *****************************************************************************
send_digit
andlw
addlw
goto

LOW_NYBBLE_MASK
ASCII_NUM_BASE
data2LCD

;
;
;
;

Mask off high nybble


Add offset for ASCII char set (0x30)
Send byte in W to LCD and return to caller
through data2LCD to save stack space

; *****************************************************************************
;
;
Function: set_cursor_pos
;
;
Purpose: Position cursor at the frequency digit position being updated.
;
;
Input:
The cur_pos variable holds the digit position.
;
;
Output: None
;
;
Revisions:
;
Ver 1.0 Copied from PICELGen 2.01. 4-20-04 W3CD
;
; *****************************************************************************
set_cursor_pos

IF LCDCHAR == 8
movlw CMD_MOV_CUR_DISP
iorwf cur_pos,w
ELSE
IF LCD16_LINEAR == 1

;
;
;
;
;

This function depends on the LCD


display being used. If it's 8
characters wide, then operation is
straightforward. It gets slightly
more involved for 16 AmQRP character LCDs.

; Cursor position base command


; OR in the cursor position

movlw
iorwf

CMD_MOV_CUR_DISP
cur_pos,w

; Cursor position base command


; Or in the cursor postion

movlw
subwf
btfsc
goto

LCD_POS_9
cur_pos,w
STATUS,C
set_cur_1

movlw
addwf
goto

CMD_MOV_CUR_DISP
cur_pos,w
set_cur_2

;
;
;
;
;
;
;
;

ELSE

set_cur_1
addlw
set_cur_2

PICEL_LONG_9

Get 9th cursor position


Determine where the cursor is
Carry is clear if cur_pos < 9th position
If cur_pos >= 8, then make some adustments
Else, use cursor address as-is
Get positioning command
Include the cursor position
Finish up

; Arrive here with delta cursor pos in W


; Add command & offset for 9th position
; Continue with cursor movement

ENDIF
ENDIF
call
cmnd2LCD
call
set_leds
return

; Move the cursor


; Restore LED inidications
;

; *****************************************************************************
;
;
Function: clear_lcd
;
;
Purpose: Clears the LCD and homes the cursor.
;
;
Input:
None
;
;
Output: None
;
;
Revisions:
;
Ver 1.0: Copied from PICELGen 2.0 4-20-04 W3CD.
;
; *****************************************************************************
clear_lcd
movlw CMD_CLEAR_LCD
call
cmnd2LCD
return
;
;
;
;
;
;
;
;
;
;
;

; Prepare to display a new message on LCD


; by clearing the current one

*****************************************************************************
*
*
* Purpose: Check if LCD is done with the last operation.
*
*
This subroutine polls the LCD busy flag to determine if
*
*
previous operations are completed.
*
*
*
* Input: None
*
*
*
* On exit: PORTB set as: RB3
input
P
*
all others outputs
P
*****************************************************************************

busy_check
clrf
bsf
movlw

PORTB
STATUS,RP0
b'00001000'

; Clear all outputs on PORTB


; Switch to bank 1 for Tristate operation
; Set RB3 input, others outputs

movwf
bcf
bcf
bsf
movlw
movwf
LCD_is_busy
bsf
movf
movwf
bcf
nop
nop
bsf
nop
bcf
decf
btfsc
goto
btfsc
goto
not_busy
return
;
;
;
;
;
;
;
;
;

TRISB
STATUS,RP0
PORTB,LCD_rs
PORTB,LCD_rw
0xFF
timer1

;
;
;
;
;
;

PORTB,LCD_e
PORTB,w
LCD_read
PORTB,LCD_e

;
;
;
;
;
;
PORTB,LCD_e
;
;
PORTB,LCD_e
;
timer1,f
;
STATUS,Z
;
not_busy
;
LCD_read,LCD_busy ;
LCD_is_busy
;

via Tristate
Switch back to bank 0
Set up LCD for Read Busy Flag (RS = 0)
Set up LCD for Read (RW = 1)
Set up constant 255
for timer loop counter
Set E high
Read PORTB into W
Save W for later testing
Drop E again
Wait a
while
Pulse E high (dummy read of lower nibble),
wait,
and drop E again
Decrement loop counter
Is loop counter down to zero?
If yes, return regardless
Busy Flag (RB3) in save byte clear?
If not, it is busy so jump back

*****************************************************************************
* Purpose: Send Command or Data byte to the LCD
*
*
Entry point cmnd2LCD: Send a Command to the LCD
*
*
Entry Point data2LCD: Send a Data byte to the LCD
*
*
*
* Input: W has the command or data byte to be sent to the LCD.
*
*
*
* Output: None
*
*****************************************************************************

cmnd2LCD

; ****** Entry point ******


movwf LCD_char
; Save byte to write to LCD
clrf
rs_value
; Remember to clear RS (clear rs_value)
bcf
PORTB,LCD_rs
; Set RS for Command to LCD
goto
write2LCD
; Go to common code
data2LCD ; ****** Entry point ********
movwf LCD_char
; Save byte to write to LCD
bsf
rs_value,0
; Remember to set RS (set bit 0 of rs_value)
bsf
PORTB,LCD_rs
; Set RS for Data to LCD
write2LCD
call
busy_check
; Check to see if LCD is ready for new data
clrf
PORTB
; Clear all of Port B (inputs and outputs)
bsf
STATUS,RP0
; Switch to bank 1 for Tristate operation
movlw 0x00
; Set up to enable PORTB data pins
movwf TRISB
; All pins (RB7..RB0) are back to outputs
bcf
STATUS,RP0
; Switch to bank 0
bcf
PORTB,LCD_rw
; Set LCD back to Write mode (RW = 0)
bcf
PORTB,LCD_rs
; Guess RS should be clear
btfsc rs_value,0
; Should RS be clear? (is bit 0 == 0?)
bsf
PORTB,LCD_rs
; No, set RS
;
; Transfer Most Significant nibble (XXXX portion of XXXXYYYY)
;
movlw

HI_NYBBLE_MASK

; Set up mask

andwf

PORTB,f

; Keep RB7..RB4 but clear old RB3..RB0

swapf
andlw
iorwf
bsf
nop
bcf

LCD_char,w
LOW_NYBBLE_MASK
PORTB,f
PORTB,LCD_e

;
;
;
;
;
;

PORTB,LCD_e
;
; Transfer Least Significant nibble
;
movlw HI_NYBBLE_MASK
;
andwf PORTB,f
;
movf
LCD_char,w
;
andlw LOW_NYBBLE_MASK ;
iorwf PORTB,f
;
bsf
PORTB,LCD_e
;
nop
;
bcf
PORTB,LCD_e
;
return

Put byte into W (reverse nibbles)


Mask to give 0000XXXX in W
To RB3..RB0 with RB7..RB4 unchanged
Pulse the E line high,
wait,
and drop it again
(YYYY portion of XXXXYYYY)
Set up mask
Clear old RB3..RB0
Move LS nibble of into W
Mask to give 0000YYYY in W
To RB3..RB0 with RB7..RB4 unchanged
Pulse the E line high,
wait,
and drop it again

; *****************************************************************************
;
;
Function: read_sweep_freqs
;
;
Purpose: Reads the start, end, step and marker frequencies stored
;
in EEPROM
;
;
Input:
None
;
;
Output: start_freq_3..0, end_freq_3..0, step_freq_3..0 and
;
marker_freq_3..0 are initialized with EEPROM data
;
freq_pointer is set to start_freq_0
;
;
Revisions:
;
Ver 1.0: New function 4-22-04 W3CD.
;
; *****************************************************************************
read_sweep_freqs
movlw
movwf
movwf
movlw
call

start_freq_0
freq_pointer
FSR
EEstart_freq
read_EEPROM_freq

;
;
;
;
;

Get pointer to starting frequency


Initial frequency data.
Save for indiect addressing
Point to starting freq in EEPROM
Read the freq

movlw
movwf
movlw
call

end_freq_0
FSR
EEend_freq
read_EEPROM_freq

;
;
;
;

Get pointer to ending frequency


Save for indiect addressing
Point to ending freq in EEPROM
Read the freq

movlw
movwf
movlw
call

step_freq_0
FSR
EEstep_freq
read_EEPROM_freq

;
;
;
;

Get pointer to frequency increment


Save for indiect addressing
Point to step freq in EEPROM
Read the freq

movlw

marker_freq_0

; Get pointer to marker frequency

movwf
movlw
call

FSR
; Save for indiect addressing
EEmarker_freq
; Point to marker freq in EEPROM
read_EEPROM_freq ; Read the freq

return
; *****************************************************************************
;
;
Function: write_sweep_freqs
;
;
Purpose: Stores start_freq_3..0, end_freq_3..0, step_freq_3..0 and
;
marker_freq_3..0 into EEPROM locations EEstart_loc,
;
EEend_loc, EEstep_loc and EEmarker_loc, respectively.
;
;
Input:
None
;
;
Output: EEPROM is updated
;
;
Revisions:
;
Ver 1.0: New function 4-22-04 W3CD.
;
; *****************************************************************************
write_sweep_freqs
movlw
movwf
movlw
call

start_freq_0
FSR
EEstart_freq
write_EEPROM_freq

;
;
;
;

Get pointer to starting frequency


Save for indiect addressing
Point to starting freq in EEPROM
Store the freq

movlw
movwf
movlw
call

end_freq_0
FSR
EEend_freq
write_EEPROM_freq

;
;
;
;

Get pointer to ending frequency


Save for indiect addressing
Point to ending freq in EEPROM
Store the freq

movlw
movwf
movlw
call

step_freq_0
FSR
EEstep_freq
write_EEPROM_freq

;
;
;
;

Get pointer to frequency increment


Save for indiect addressing
Point to step freq in EEPROM
Store the freq

movlw
movwf
movlw
call

marker_freq_0
FSR
EEmarker_freq
write_EEPROM_freq

;
;
;
;

Get pointer to marker frequency


Save for indiect addressing
Point to marker freq in EEPROM
Store the freq

return
; *****************************************************************************
;
;
Function: read_EEPROM_freq
;
;
Purpose: Reads a 4-byte frequency value from EEPROM
;
;
Input:
FSR points to the LSB into which the frequency is copied
;
w points to LSB of EEPROM frequency data entry
;
;
Output: The 4-byte target is updated with EEPROM frequency
;

;
Revisions:
;
Ver 1.0: Copied from PICELGen 2.0 4-20-04 by W3CD.
;
Ver 1.1: Adapted to PIC16F628A. 10-04-05 W3CD
;
; *****************************************************************************
read_EEPROM_freq
movwf ee_addr
movlw FREQ_COUNT
movwf count

; Save the address


; Get the byte count
; into loop counter

read_EEPROM_loop
call
read_EEPROM
movwf INDF
incf
ee_addr,f
incf
FSR,f
decfsz count,f
goto
read_EEPROM_loop
return

;
;
;
;
;
;
;

Read a byte
Store new frequency byte
Increment EEPROM read address
Point to next target address
Decrement byte count
Loop if more bytes remaining.
Back to caller

; ****************************************************************************
;
;
Function: write_EEPROM_freq
;
;
Purpose: This function saves a 4-byte frequency value in EEPROM.
;
;
Input: FSR points to the LSB of the frequency data to store
;
w points to LSB of EEPROM frequency data entry
;
;
Output: none
;
;
Revisions:
;
Ver 1.0: New function. 4-22-04 W3CD
;
Ver 1.1: Adapted to PIC16F628A. 10-04-05 W3CD
;
; ****************************************************************************
write_EEPROM_freq
movwf ee_addr
movlw FREQ_COUNT
movwf count
write_EE_loop
movf
call
incf
incf
decfsz
goto
return
;
;
;
;
;

INDF,w
write_EEPROM
FSR,f
ee_addr,f
count,f
write_EE_loop

; Save the write address


; Prepare loop to write contents
; of X_freq_0..3 into EEPROM
;
;
;
;
;
;
;
;

Get the data to be written


Do the write thing
Bump freq pointer
Increment EEPROM write address
Decrement byte count
Iterate while bytes remain to be written
Fall through when done
Done!

*****************************************************************************
*
*
* Purpose: Write the byte of data at EEDATA to the EEPROM at address
*
*
EEADR.
*
*
*

;
;
;
;
;
;
;
;

* Input: ee_addr contains the EEPROM write address


*
*
w contains the write data
*
*
*
* Output: The EEPROM location is updated.
*
*
*
* Revisions:
*
*
Ver 1.1: Adapted to PIC16F628A. 10-04-05 W3CD
*
*****************************************************************************

write_EEPROM
bsf
movwf
movf
movwf
bsf
movlw
movwf
movlw
movwf
bsf
bit_check
btfsc
goto
bcf
bcf
return
;
;
;
;
;
;
;
;
;

STATUS,RP0
EEDATA
ee_addr,w
EEADR

;
;
;
;

Switch to bank 1
Store the write data
Get the write address
Prepare EE address

EECON1,WREN
0x55
EECON2
0xAA
EECON2
EECON1,WR

; Set the EEPROM write enable bit


; Write 0x55 and 0xAA to EEPROM
; control register, as required
; for the write
;
; Set WR to initiate write

EECON1,WR
bit_check
EECON1,WREN
STATUS,RP0

;
;
;
;
;

Has the write completed?


No, keep checking
Clear the EEPROM write enable bit
Switch to bank 0
Return to the caller

*****************************************************************************
*
*
* Purpose: Read a byte of EEPROM data at address EEADR into EEDATA.
*
*
*
* Input: The address EEADR.
*
*
*
* Output: The value in EEDATA.
*
*
*
*****************************************************************************

read_EEPROM
bsf
movf
movwf
bsf
movf
bcf
return

STATUS,RP0
ee_addr,w
EEADR
EECON1,RD
EEDATA,w
STATUS,RP0

;
;
;
;
;
;
;

Switch to bank 1
Get read address
Copy to EE register
Request the read
Get the data
Switch to bank 0
Return to the caller

; *****************************************************************************
;
;
Function: set_leds
;
;
Purpose: Restores the static LED states on Port B after display updates
;
and DDS daughter card writes.
;
;
Input:
led_states
;
;
Output: LEDs are restored to their static values
;

;
Revisions:
;
Ver 1.0 Copied from PICELGen 2.0 4-20-04 W3CD
;
; *****************************************************************************
set_leds
movlw
andwf
movf
iorwf
return

LED_MASK
PORTB,F
led_states,w
PORTB,f

;
;
;
;
;

Get led mask


Apply it to Port B
Get the led states
Apply them to Port B
Done

; *****************************************************************************
; *
*
; * Purpose: Wait for a specified number of milliseconds.
*
; *
*
; *
Entry point wait_a_sec: Wait for 1 second
P
; *
Entry point wait_256ms: Wait for 256 msec
P
; *
Entry point wait_128ms: Wait for 128 msec
*
; *
Entry point wait_64ms : Wait for 64 msec
*
; *
Entry point wait_32ms : Wait for 32 msec
*
; *
Entry point wait_16ms : Wait for 16 msec
*
; *
Entry point wait_8ms : Wait for 8 msec
*
; *
Entry point wait_1ms
*
; *
*
; * Input: None
*
; *
*
; * Output: None
*
; *
*
; *****************************************************************************
;
wait_a_sec ; ****** Entry point ******
call
wait_256ms
;
call
wait_256ms
;
wait_512ms ; ****** Entry point ******
call
wait_256ms
;
call
wait_256ms
;
return
wait_256ms ; ****** Entry point ******
call
wait_128ms
;
call
wait_128ms
;
return
wait_128ms ; ****** Entry point ******
movlw 0xFF
; Set up outer loop
movwf timer1
; counter to 255
goto
outer_loop
; Go to wait loops
wait_64ms ; ****** Entry point ******
movlw 0x80
; Set up outer loop
movwf timer1
; counter to 128
goto
outer_loop
; Go to wait loops
wait_32ms ; ****** Entry point ******
movlw 0x40
; Set up outer loop
movwf timer1
; counter to 64
goto
outer_loop
; Go to wait loops
wait_16ms ; ****** Entry point ******
movlw 0x20
; Set up outer loop
movwf timer1
; counter to 32
goto
outer_loop
; Go to wait loops

wait_8ms

; ****** Entry point ******


movlw 0x10
; Set up outer loop
movwf timer1
; counter to 16
goto
outer_loop
; Into the wait loops

wait_1ms
movlw
movwf

0x2
timer1

;
; Wait loops used by other wait routines
; - 1 microsecond per instruction (with a 4 MHz microprocessor crystal)
; - 510 instructions per inner loop
; - (Timer1 * 514) instructions (.514 msec) per outer loop
; - Round off to .5 ms per outer loop
;
outer_loop
movlw 0xFF
; Set up inner loop counter
movwf timer2
; to 255
inner_loop
decfsz timer2,f
; Decrement inner loop counter
goto
inner_loop
; If inner loop counter not down to zero,
; then go back to inner loop again
decfsz timer1,f
; Yes, Decrement outer loop counter
goto
outer_loop
; If outer loop counter not down to zero,
; then go back to outer loop again
return
; Yes, return to caller
;
; *****************************************************************************
;
END

You might also like