by Jan Panteltje
From: http://panteltje.com/panteltje/pic/freq_pic/
Very nice "Dead Bug" Construction!
;**************************************************************************
; FILE: counter.asm *
; CONTENTS: Simple low-cost digital frequency meter using a PIC 16F628 *
; AUTHOR: Wolfgang Buescher, DL4YHF *
; (based on a work by James Hutchby, MadLab, 1996) *
; REVISIONS: (latest entry first) *
; 2009-08-27 - Added RS232 output option 1200 Bd by Jan Panteltje *
; 2006-05-31 - Added the 'power-save' option which temporarily puts the *
; PIC to sleep (with only the watchdog-oscillator running) *
; 2006-05-15 - New entry in the preconfigured frequency table for 4-MHz *
; IF filters (like "Miss Mosquita" [Moskita] by DK1HE) *
; 2005-08-24 - Cured a bug in the COMMON ANODE decimal point setting . *
; (the "^0xFF" for the AND-mask was missing in macro 'conv') *
; 2005-03-21 - Added a few conditionals to use the same sourcecode *
; to drive a COMMON ANODE display ( DISPLAY_VARIANT_3 ) *
; 2004-03-14 - Fixed a range-switching bug around 8 MHz . *
; - Support TWO different display variants now, *
; optimized for different board layouts, and different clock *
; frequencies (4 MHz for variant 1, 20 MHz for variant 2). *
; 2004-03-05 - Added the feature to add or subtract a frequency offset . *
; 2004-02-18 - Migration to a PIC16F628 with 4 MHz crystal (el Cheapo) *
; - Changed the LED patterns '6' and '9' because they looked *
; like 'b' and 'q' in the old counter version . *
; - Added the auto-ranging feature *
; - Stepped from 24-bit to 32-bit integer arithmetic, to be *
; able to count 50 MHz with 1-second gate time, *
; or (at least) adjust ANY result for the ANY prescaler *
; division ratio, which may give pretty large numbers . *
; - A PIC16F628 worked up to 63 MHz with this firmware . *
;**************************************************************************
list P=16F648a
; #include <p16F628.inc> ; processor specific definitions
#include <p16f648a.inc> ; processor specific definitions
; Uncomment this if you want RS232 output
#define RS232_OUT
; Uncomment the next line if you want pin 8 of the PIC to directly drive a RS232 line (without MAX232 or such).
#define NON_INVERTING_RS232_OUT
; BAUD_DIVIDER set to 1 for 9600 Bd, 2 for 4800 Bd, 4 for 2400 Bd, 8 for 1200 Bd, 16 for 600 Bd, 32 for 300 Bd, 64 for 150 Bd, and 128 for 75 Bd.
; Note: for 9600 Bd set BIT_DELAY to 27, or basically a bit lower then for the other baudrates, due to more relative time spend in other instructions.
#define BAUD_DIVIDER d'8' ; 1200 Baud
; set baudrate, for small deviations of the internal oscillator this may need to be adapted.
#define BIT_DELAY d'165' ; approx 165 for 1200 Bd with 20 MHz clock
; for software RS232 out, NOT using UART
#define RS232_PORT PORTB
#define RS232_BIT D'2' ; RB2, pin 8
; print options, all field active looks like:
; 00000050 50 Hz 0.050 kHz
; 19999968 19,999,968 Hz 19.999 MHz
; Uncomment this if you want the first field printed.
#define RS232_PRINT_FIELD_1
; Uncomment this if you want the secind field printed.
#define RS232_PRINT_FIELD_2
; Uncomment this if you want the third field printed.
;#define RS232_PRINT_FIELD_3
; Note: The first field is for parsing by user space programs. it is recommanded to always leave the field active.
; for unit printing
#define KILOHERTZ_FLAG 1
#define NO_INPUT_FLAG 2
#define ZERO_SUPPRESSION_FLAG 4
#define DISPLAY_VARIANT_2
#define DEBUG 0 ; DEBUG=1 for simulation, DEBUG=0 for real hardware
; Selection of LED display control bits... since 2005, three different variants.
; Select ONE OF THESE in MPLAB under "Project".."Build Options".."Macro Definitions"!
; DISP_VARIANT=1 : first prototype, PIC on left side of display
; DISP_VARIANT=2 : second prototype, separated PIC and display board
; DISP_VARIANT=3 : similar as (2), but for COMMON CATHODE display
; Unfortunately it seems impossible to assign a NUMERIC VALUE to a macro
; in MPLAB (not in MPASM!) ....
#ifdef DISPLAY_VARIANT_1
#define DISP_VARIANT 1 ; very first (old) prototype by DL4YHF
#define COMMON_ANODE 0
#define COMMON_CATHODE 1
#else
#ifdef DISPLAY_VARIANT_2
#define DISP_VARIANT 2 ; 5 digits, new layout, COMMON CATHODE
#define COMMON_ANODE 0
#define COMMON_CATHODE 1
#else
#ifdef DISPLAY_VARIANT_3 ; added 2005-03-21 :
#define DISP_VARIANT 3 ; similar as (2), but for COMMON ANODE display
#define COMMON_ANODE 1
#define COMMON_CATHODE 0
#else
#define DISP_VARIANT 4
#define COMMON_ANODE 0
#define COMMON_CATHODE 1
; "Error, Must define DISPLAY_VARIANT_1, .._2, or .._3 under project options"
; With MPLAB: Project..Build Options..Project..MPASM..Macro Definitions..Add
#endif
#endif
#endif
;**************************************************************************
; *
; Summary *
; *
;**************************************************************************
; The software functions as a frequency meter with an input signal
; range of 1 Hz to ~ 50 MHz and with an accuracy of +/- 1Hz
; if the oscillator crystal is properly trimmed .
; Signal pulses are counted over a fixed time interval of 1/4 second to
; 1 second (gate time). High frequency pulses are counted over 1/4 s
; to make the meter more responsive with no loss of displayed accuracy.
; Pulses are counted using Timer 0 of the PIC,
; which is set to increment on rising edges on the TMR0 pin. The 8-bit
; hardware register is extended by software into a 32-bit pulse counter.
; If timer 0 rolls over (msb 1 -> 0) between successive polls then the
; high two bytes of the pulse counter are incremented.
; Timer 0 is unable to count more than one pulse per instruction cycle
; (per 4 clock cycles) so the prescaler is used at frequencies above
; 1MHz (4MHz clock / 4) and also to ensure that pulses are not lost
; between polls of timer 0 (which would happen if more than 128 pulses were
; received). Fortunately the prescaler is an asynchronous counter
; which works up to a few ten MHz (sometimes as far as 60 MHz) .
; Timing is based on a software loop of known execution period . The loop
; time is 50 or 20 us which gives integer counts to time 1 s and 1/4 s .
; During this timing loop, the multiplexed LED display is updated .
; The frequency in binary is converted to decimal using a powers-of-ten
; lookup table. The binary powers of ten are repeatedly subtracted from
; the frequency to determine the individual decimal digits. The decimal
; digits are stored at the 8 bytes at 'digits'. Leading zeroes are then
; suppressed and the 4 (or 5) significant digits are converted to LED data
; for the 7-segment displays using a lookup table.
; The signal frequency is displayed on four (or five) 7-segment displays.
; The displays are multiplexed which means that only one display is enabled
; at any one time. The variable 'disp_index' contains the index of the currently
; enabled display. Each display is enabled in turn at a sufficient frequency
; that no flicker is discernable. A prescaler ('disp_timer') is used
; to set the multiplexing frequency to a few hundred Hz.
; The display shows the signal frequency in KHz or MHz, according to the
; following table:
; --------------------------
; | Frequency | Display |
; --------------------------
; | < 1Hz | 0 |
; | 1Hz | 0.001[0] | Note: kHz-dot is flashing (blinking)
; | 10Hz | 0.010[0] |
; | 100Hz | 0.100[0] |
; | 1.000KHz | 1.000[0] |
; | 10.00KHz | 10.00[0] |
; | 100.0KHz | 100.0[0] |
; | 1.000MHz | 1.000[0] | Note: MHz-dot is steady (not blinking)
; | 10.00MHz | 10.00[0] |
; --------------------------
; If there is no signal at all, a single zero is displayed in the 4th(!) digit.
; Overflows are not displayed because they cannot be detected !
;**************************************************************************
; *
; PIC config definitions *
; *
;**************************************************************************
; '__CONFIG' directive is used to embed configuration data within .asm file.
; The lables following the directive are located in the respective .inc file.
; See respective data sheet for additional information on configuration word.
; Since 2006-05-28, the watchdog must be ENABLE in the config word
; because of its wakeup-from-sleep function (see 'Sleep100ms') .
; EX(16F84:) __CONFIG _CP_OFF & _WDT_ON & _PWRTE_ON & _RC_OSC
#if (DISP_VARIANT==1) ; display variant 1 : clocked with 4 MHz (low power, "XT" )
__CONFIG _CP_OFF & _WDT_ON & _PWRTE_ON & _XT_OSC & _LVP_OFF & _BODEN_OFF & _MCLRE_OFF
#else ; display variants 2+3 : clocked with 20 MHz (needs "HS" oscillator)
__CONFIG _CP_OFF & _WDT_ON & _PWRTE_ON & _HS_OSC & _LVP_OFF & _BODEN_OFF & _MCLRE_OFF
#endif
; '__IDLOCS' directive may be used to set the 4 * 4(?!?) ID Location Bits .
; These shall be placed in the HEX file at addresses 0x2000...0x2003 .
__IDLOCS H'1234'
; (definitions of "file" registers removed. They are defined in a header file!)
;**************************************************************************
; *
; Port assignments *
; *
;**************************************************************************
PORT_A_IO equ b'0000' ; port A I/O mode (all output)
PORT_B_IO equ b'00000000' ; port B I/O mode (all output)
LEDS_PORT equ PORTB ; 7-segment LEDs port
ENABLE_PORT equ PORTA ; display enable port
; Bitmasks to control the digit outputs have been moved to enable_table .
; YHF: Note that 'display #0' is the MOST SIGNIFICANT digit !
#define IOP_PROG_MODE PORTA,5 ; digital input signal, LOW enters programming mode
;**************************************************************************
; *
; Constants and timings *
; *
;**************************************************************************
; processor clock frequency in Hz (4MHz)
#if (DISP_VARIANT==1) ; display variant 1 : clocked with 4 MHz (low power consumption)
CLOCK equ .4000000
#else ; display variants 2+3 : clocked with 20 MHz (higher resolution)
CLOCK equ .20000000
#endif
; microseconds per timing loop
#if (DISP_VARIANT==1) ; display variant 1 : clocked with 4 MHz
; 20 microseconds is impossible with 4-MHz-Crystal, so use 50 us instead !
; Make sure all gate times can be divided by this interval without remainder :
; 1 second / 50 us = 20000 (ok)
; 1/4 second / 50 us = 5000 (ok)
; 1/8 second / 50 us = 2500 (ok)
TIME equ .50
#else ; display variants 2+3 : clocked with 20 MHz
; 20 microseconds is impossible with 4-MHz-Crystal, so use 50 us instead !
; Make sure all gate times can be divided by this interval without remainder :
; 1 second / 20 us = 50000 (ok)
; 1/4 second / 20 us = 12500 (ok)
; 1/8 second / 50 us = 6250 (ok)
TIME equ .20
#endif ; variant 1 or 2+3 ?
; Clock cycles per timing loop. See subroutine count_pulses .
; Usually CYCLES=200 (for 4 MHz crystal, 50 usec - loop)
; or 400 (for 20 MHz crystal, 20 usec - loop)
CYCLES equ TIME*CLOCK/.1000000
GATE_TIME_LOOPS equ CLOCK/CYCLES ; number of gate-time loops for ONE SECOND gate time
LAMPTEST_LOOPS equ CLOCK/(.2*CYCLES) ; number of loops for a 0.5 SECOND lamp test after power-on
PROGMODE_LOOPS equ CLOCK/(.10*CYCLES) ; number of delay loops for display in PROGRAMMING MODE (0.1 sec)
; Configuration of power-saving mode :
#if( DEBUG )
PSAVE_DELAY_TIME equ .10 ; number of 0.25-sec-intervals before turning off (shorter for debugging)
#else
PSAVE_DELAY_TIME equ .60 ; number of 0.25-sec-intervals before turning off (some dozen seconds)
#endif
PSAVE_FLASHUP_TIME equ .14 ; number of 0.7(!)-second-intervals between two flashes in power-saving mode
PSAVE_MAX_DIFF equ .10 ; maximum frequency difference (range-dependent, see below)
; Unit: N times "frequency-resolution", see frequency-range table .
; Example: PSAVE_MAX_DIFF=10 means 10*4Hz in Range 1 (1..3.4 MHz) .
; Menu Indices ... must match the jump table PMDisplay + PMExecute !
MI_QUIT equ 0 ; exit from menu
MI_PSAVE equ 1 ; turn "power save"-option on and off
MI_ADD equ 2 ; save frequency offset to ADD it from now on
MI_SUB equ 3 ; save frequency offset to SUBTRACT it from now on
MI_ZERO equ 4 ; set the frequency offset to ZERO and leave menu
MI_STD_IF equ 5 ; jump into the STANDARD INTERMEDIATE FREQUENCY table..
MI_INDEX_MAX equ 5 ; normal menu indices up to MI_INDEX_MAX .
MI_IF_1 equ 6 ; show the 1st standard IF
MI_IF_2 equ 7 ; show the 2nd standard IF
MI_IF_3 equ 8 ; show the 3rd standard IF
MI_IF_4 equ 9 ; show the 4th standard IF
MI_IF_5 equ 0x0A ; show the 4th standard IF
MI_IF_QT equ 0x0B ; exit standard IF menu without changing anything
MI_IF_SUBMENU_MAX equ 0x0A
;**************************************************************************
; *
; File register usage *
; *
;**************************************************************************
; RAM memory (general purpose registers, unfortunately not the same for PIC16F84 & PIC16F628)
; in PIC16F628: RAM from 0x20..0x7F (96 bytes, 0x20.. only accessable in Bank0)
; 0xA0..0xEF (another 80 bytes in Bank1)
; 0x120..0x14F (another 48 bytes in Bank2)
; 0x0F0..0x0FF, 0x170..0x17F , 0x1F0..0x1FF are mapped to 0x70..0x7F (same in all banks)
; So use 0x70..0x7F for context saving in the PIC16F628 and forget 0x0F0.. 0xNNN !
;
; Note on the 32-bit integer arithmetics as used in this code:
; - They begin with MOST SIGNIFICANT BYTE in memory, but...
; - Every byte location has its own label here, which makes debugging
; with Microchip's simulator much easier (point the mouse on the name
; of a variable to see what I mean !)
;
tens_index equ 0x27 ; index into the powers-of-ten table
divi equ 0x28 ; power of ten (32 bits)
divi_hi equ 0x28 ; same as 'divi' : HIGH byte
divi_mh equ 0x29 ; MEDIUM HIGH byte
divi_ml equ 0x2A ; MEDIUM LOW byte
divi_lo equ 0x2B ; LOW byte
timer0_old equ 0x2C ; previous reading from timer0 register
gatecnt_hi equ 0x2D ; 16-bit counter (msb first)
gatecnt_lo equ 0x2E ; 16-bit counter (lsb last)
bTemp equ 0x2F ; temporary 8-bit register,
; may be overwritten in ALL subroutines
freq equ 0x30 ; frequency in binary (32 bits)....
freq_hi equ 0x30 ; same location, begins with HIGH byte
freq_mh equ 0x31 ; ... medium high byte
freq_ml equ 0x32 ; ... medium low byte
freq_lo equ 0x33 ; ... low byte
freq2 equ 0x34 ; frequency too, copied for programming mode
freq2_hi equ 0x34 ; same location, begins with HIGH byte
freq2_mh equ 0x35 ; ... medium high byte
freq2_ml equ 0x36 ; ... medium low byte
freq2_lo equ 0x37 ; ... low byte
foffs equ 0x38 ; frequency too, copied for programming mode
foffs_hi equ 0x38 ; same location, begins with HIGH byte
foffs_mh equ 0x39 ; ... medium high byte
foffs_ml equ 0x3A ; ... medium low byte
foffs_lo equ 0x3B ; ... low byte
menu_index equ 0x3C ; menu item for programming mode
menu_timer equ 0x3D ; used to detect how long a key was pressed
digits equ 0x40 ; frequency as decimal digits (8 bytes)...
digit_0 equ 0x40 ; same location as MOST SIGNIFICANT digit, 10-MHz
digit_1 equ 0x41 ; usually the 1-MHz-digit
digit_2 equ 0x42 ; usually the 100-kHz-digit
digit_3 equ 0x43 ; usually the 10-kHz-digit
digit_4 equ 0x44 ; usually the 1-kHz-digit
digit_5 equ 0x45 ; usually the 100-Hz-digit
digit_6 equ 0x46 ; usually the 10-Hz-digit
digit_7 equ 0x47 ; usually the 1-Hz-digit
digit_8 equ 0x48 ; must contain a blank character (or trailing zero)
display0 equ 0x49 ; display #0 data
display1 equ 0x4A ; display #1 data
display2 equ 0x4B ; display #2 data
display3 equ 0x4C ; display #3 data
display4 equ 0x4D ; display #4 data
disp_index equ 0x4E ; index of the enabled display (0 to 4 for 5-digit display)
disp_timer equ 0x4F ; display multiplex timer (5 bits)
adjust_shifts equ 0x50 ; count of 'left shifts' to compensate prescaler+gate time
blinker equ 0x51 ; prescaler for the flashing 1-kHz-dot
psave_timer equ 0x52 ; timer for power-save mode (incremented every 0.25 seconds)
psave_freq_lo equ 0x53 ; low-byte of frequency to detect changes for power-save mode
psave_flags equ 0x54 ; power-saving flags with the following bits:
#define PSFLAG_ACTIVE psave_flags,0 ; clear:normal mode, set:power-saving in action (display blanked)
options equ 0x55 ; display options with the following flag-bits:
#define OPT_PWRSAVE options,0 ; clear:normal mode, set:power-saving mode enabled
tx_reg equ 0x56
bit_count equ 0x57
baud_divider equ 0x58
delay_counter equ 0x59
print_flags equ 0x5A
temp equ 0x5B
;**************************************************************************
; *
; Macros (1) *
; *
;**************************************************************************
eep_dw macro value ; a DOUBLEWORD split into 4 bytes in the PIC's DATA EEPROM
de (value>>.24), (value>>.16)&0xFF, (value>>8)&0xFF, value&0xFF
endm
;**************************************************************************
; *
; EEPROM memory definitions *
; *
;**************************************************************************
; for PIC16F84: 0x00..0x3F were valid EEPROM locations (64 byte)
; for PIC16F628: 0x00..0x7F are valid EEPROM locations (128 byte)
#define EEPROM_ADR_FREQ_OFFSET 0x00 ; EEPROM location for frequency offset
#define EEPROM_ADR_STD_IF_TABLE 0x04 ; EEPROM location for standard IF table (4*4 byte)
#define EEPROM_ADR_OPTIONS 0x20 ; EEPROM location for "options" (flags)
; This gives warnings with gpasm 'Warning [220] Address exceeds maximum range for this processor.'
; Initial contents of DATA EEPROM:
org (0x2100+EEPROM_ADR_FREQ_OFFSET)
eep_dw .0 ; [00..03] initial frequency offset = ZERO
org (0x2100+EEPROM_ADR_STD_IF_TABLE) ; standard IF table ...
eep_dw .455000 ; [04..07] frequently used in old AM radios
eep_dw .3999000 ; [08..0B] used in "Miss Mosquita" (DK1HE / DL QRP AG)
eep_dw .4194304 ; [0C..0F] used in other homebrew amateur radio receivers
eep_dw .4433619 ; [10..13] sometimes used in homebrew amateur radio receivers
eep_dw .10700000 ; [14..17] frequently used in old FM radios
; [18..1F] reserved for other "preprogrammed" values
org (0x2100+EEPROM_ADR_OPTIONS)
de .0 ; [20] "options" (flags), cleared by default
;**************************************************************************
; *
; More Macros *
; *
;**************************************************************************
;--------------------------------------------------------------------------
; macros to implement lookup tables - these macros hide the PIC syntax
; used and make the source code more readable
; (YHF: CAUTION - BUT THESE MACROS HIDE SOME VERY NASTY PITFALLS .
; TABLE MUST NOT CROSS PAGE BORDER DUE TO 'ADDWF PCL, f' ! )
;--------------------------------------------------------------------------
cquad macro value
retlw value>>.24 ; high byte
retlw (value>>.16)&0xFF ; middle-high byte
retlw (value>>8)&0xFF ; middle-low byte
retlw value&0xFF ; low byte
endm
table macro label ; define lookup table
label addwf PCL,f ; caution: this is 'PCL' only, cannot add to the full 'PC' in a PIC !
endm
;--------------------------------------------------------------------------
; add with carry - adds the w register and the carry flag to the file
; register reg, returns the result in <reg> with the carry flag set if overflow
;--------------------------------------------------------------------------
addcwf macro reg
local add1,add2
bnc add1 ; branch if no carry set
addwf reg , f ; add byte
incf reg , f ; add carry
skpnz
setc
goto add2
add1 addwf reg,f ; add byte
add2
endm
;--------------------------------------------------------------------------
; subtract with "no-carry" - subtracts the w register and the no-carry flag
; from the file register reg, returns the result in reg with the no carry flag
; set if underflow
;--------------------------------------------------------------------------
subncwf macro reg
local sub1,sub2
bc sub1 ; branch if carry set
subwf reg, f ; subtract byte
skpnz ; subtract no carry
clrc
decf reg , f
goto sub2
sub1 subwf reg , f ; subtract byte
sub2
endm
;--------------------------------------------------------------------------
; MACRO to perform 32-bit addition - adds the four bytes at op2 to the
; three bytes at op1 (most significant bytes first), returns the result in
; op1 with op2 unchanged and the carry flag set if overflow
;--------------------------------------------------------------------------
add32 macro op1,op2 ; op1 := op1 + op2
movfw op2+3 ; add low byte (bits 7...0)
addwf op1+3,f
movfw op2+2 ; add middle-low byte (bits 15..8)
addcwf op1+2
movfw op2+1 ; add middle-high byte (bits 23...16)
addcwf op1+1
movfw op2+0 ; add high byte (bits 31...24)
addcwf op1+0
endm
;--------------------------------------------------------------------------
; MACRO to perform 32-bit subtraction - subtracts the four bytes at op2
; from the four bytes at op1 (most significant bytes first), returns the
; result in op1 with op2 unchanged and the no carry flag set if underflow
;--------------------------------------------------------------------------
sub32 macro op1,op2 ; op1 := op1 - op2
movfw op2+3 ; subtract low byte
subwf op1+3 , f
movfw op2+2 ; subtract middle low byte
subncwf op1+2
movfw op2+1 ; subtract middle high byte
subncwf op1+1
movfw op2+0 ; subtract high byte
subncwf op1+0
endm
;--------------------------------------------------------------------------
; MACRO to negate a 32-bit value ( op := 0 - op ) .
;--------------------------------------------------------------------------
neg32 macro op ; op1 := 0 - op2
local neg_done
comf op, f ; invert all 8 bits in high byte
comf op+1, f ; invert all 8 bits in middle high byte
comf op+2, f ; invert all 8 bits in middle low byte
comf op+3, f ; invert all 8 bits in low byte
; Note at this point 0x000000 would have turned into 0xFFFFFFF .
; Must add ONE to complete the TWO's COMPLIMENT calculation ( -0 = 0 ).
; Note that "incf" affects only the Z flag but not the C flag .
incfsz op+3, f ; increment low byte (bits 7...0)
goto neg_done ; if incremented result NOT zero, we're through !
incfsz op+2, f ; increment middle low byte (bits 15...8)
goto neg_done ; if incremented result NOT zero, ...
incfsz op+1, f ; increment middle high byte (bits 23...16)
goto neg_done ; if ...
incfsz op+0, f ; increment high byte (bits 31...24)
goto neg_done ;
neg_done
endm
;**********************************************************************
ORG 0x000 ; processor reset vector
goto MainInit ; go to beginning of program
; (begin of ROM is too precious to waste for ordinary code, see below...)
;**************************************************************************
; *
; Lookup tables *
; Must be at the start of the code memory to avoid crossing pages !! *
; *
;**************************************************************************
;--------------------------------------------------------------------------
; 7-segment LED data table
;--------------------------------------------------------------------------
; Index 0..9 used for decimal numbers, all other indices defined below :
CHAR_A equ .10 ; Letters A..F = HEX digits, index 10..15
CHAR_b equ .11 ;
CHAR_c equ .12 ;
CHAR_d equ .13 ;
CHAR_E equ .14 ;
CHAR_F equ .15 ;
CHAR_G equ .16 ; Other letters used in "programming" mode
CHAR_H equ .17 ;
CHAR_i equ .18 ;
BLANK equ .19 ; blank display
TEST equ .20 ; power-on display test
CHAR_P equ .21 ; A few other letters for programming mode...
CHAR_r equ .22 ;
CHAR_o equ .23 ; "Prog"
CHAR_Q equ .24 ; "Quit"
CHAR_u equ .25 ;
CHAR_t equ .26 ;
CHAR_S equ .27 ; "Sub"
CHAR_Z equ .28 ; "ZEro"
CHAR_I equ .29 ; large "I" (left aligned!) for "IF"
CHAR_J equ .30 ;
CHAR_k equ .31 ;
CHAR_L equ .32 ;
CHAR_N equ .33 ;
CHAR_V equ .34 ;
CHAR_EQ equ .35 ; "="
#if (DISP_VARIANT==1)
DPPOINT_BIT equ 4 ; decimal point bit (same for all digits)
#define _A 0x01 ; bitmask for segment A , etc ..
#define _B 0x02
#define _C 0x20
#define _D 0x08
#define _E 0x04
#define _F 0x40
#define _G 0x80
#define _DP 0x10
#endif ; DISPLAY VARIANT #1
#if (DISP_VARIANT==2) || (DISP_VARIANT==3)
DPPOINT_BIT equ 1 ; decimal point bit (same for all digits)
#define _A 0x40 ; bitmask for segment A , etc ..
#define _B 0x80
#define _C 0x04
#define _D 0x01
#define _E 0x08
#define _F 0x10
#define _G 0x20
#define _DP 0x02
#endif ; DISPLAY VARIANT #2 + #3
BLANK_PATTERN equ b'00000000' ; blank display pattern (7-segment code)
;-----------------------------------------------------------------------------
; Table to convert a decimal digit or a special character into 7-segment-code
; Note: In DL4YHF's PIC counter, all digits have the same segment connections,
; so we do not need individual conversion tables for all segments.
;
; AAAA
; F B
; F B
; GGGG
; E C
; E C
; DDDD DP
;
;-----------------------------------------------------------------------------
Digit2SevenSeg:
addwf PCL,f ; caution: this is 'PCL' only, not 'PC'. Beware of page borders.
; A = 0, B = 1, C = 5, D = 3, E = 2, F = 6, G = 7, DP = 4
#if (COMMON_ANODE)
#define SSEG_XORMASK 0xFF ; since 2005-03-21 ... never tested by the author !
#else
#define SSEG_XORMASK 0x00 ; for COMMON CATHODE: No bitwise EXOR to the pattern
#endif
retlw (_A+_B+_C+_D+_E+_F )^SSEG_XORMASK ; ABCDEF. = '0' ( # 0 )
retlw ( _B+_C )^SSEG_XORMASK ; .BC.... = '1' ( # 1 )
retlw (_A+_B +_D+_E +_G)^SSEG_XORMASK ; AB.DE.G = '2' ( # 2 )
retlw (_A+_B+_C+_D +_G)^SSEG_XORMASK ; ABCD..G = '3' ( # 3 )
retlw ( _B+_C +_F+_G)^SSEG_XORMASK ; .BC..FG = '4' ( # 4 )
retlw (_A +_C+_D +_F+_G)^SSEG_XORMASK ; A.CD.FG = '5' ( # 5 )
retlw (_A +_C+_D+_E+_F+_G)^SSEG_XORMASK ; A.CDEFG = '6' ( # 6 )
retlw (_A+_B+_C )^SSEG_XORMASK ; ABC.... = '7' ( # 7 )
retlw (_A+_B+_C+_D+_E+_F+_G)^SSEG_XORMASK ; ABCDEFG = '8' ( # 8 )
retlw (_A+_B+_C+_D +_F+_G)^SSEG_XORMASK ; ABCD.FG = '9' ( # 9 )
retlw (_A+_B+_C +_E+_F+_G)^SSEG_XORMASK ; ABC.EFG = 'A' ( # 10 )
retlw ( _C+_D+_E+_F+_G)^SSEG_XORMASK ; ..CDEFG = 'b' ( # 11 )
retlw ( _D+_E +_G)^SSEG_XORMASK ; ...DE.G = 'c' ( # 12 )
retlw ( _B+_C+_D+_E +_G)^SSEG_XORMASK ; .BCDE.G = 'd' ( # 13 )
retlw (_A +_D+_E+_F+_G)^SSEG_XORMASK ; A..DEFG = 'E' ( # 14 )
retlw (_A +_E+_F+_G)^SSEG_XORMASK ; A...EFG = 'F' ( # 15 )
retlw (_A +_C+_D+_E+_F )^SSEG_XORMASK ; A.CDEF. = 'G' ( # 16 )
retlw ( _B+_C +_E+_F+_G)^SSEG_XORMASK ; .BC.EFG = 'H' ( # 17 )
retlw ( _E )^SSEG_XORMASK ; ....E.. = 'i' ( # 18 )
retlw (BLANK_PATTERN )^SSEG_XORMASK ; ....... = ' ' ( # 19 )
retlw (b'11111111' )^SSEG_XORMASK ; all segments on ( # 20 )
; A few more letters for programming mode :
retlw (_A+_B +_E+_F+_G)^SSEG_XORMASK ; AB..EFG = 'P' ( # 21 )
retlw ( _E +_G)^SSEG_XORMASK ; ....E.G = 'r' ( # 22 )
retlw ( _C+_D+_E +_G)^SSEG_XORMASK ; ..CDE.G = 'o' ( # 23 )
retlw (_A+_B+_C +_F+_G)^SSEG_XORMASK ; ABC..FG = 'Q' ( # 24 )
retlw ( _C+_D+_E )^SSEG_XORMASK ; ..CDE.. = 'u' ( # 25 )
retlw ( _D+_E+_F+_G)^SSEG_XORMASK ; ...DEFG = 't' ( # 26 )
retlw (_A +_C+_D +_F+_G)^SSEG_XORMASK ; A.CD.FG = 'S' ( # 27 )
retlw (_A+_B +_D+_E +_G)^SSEG_XORMASK ; AB.DE.G = 'Z' ( # 28 )
retlw ( _E+_F )^SSEG_XORMASK ; ....EF. = 'I' ( # 29 )
retlw ( _B+_C+_D )^SSEG_XORMASK ; .BCD.. = 'J' ( # 30 )
retlw ( _D+_E+_F+_G)^SSEG_XORMASK ; ...DEFG = 'k' ( # 31 )
retlw ( _D+_E+_F )^SSEG_XORMASK ; ...DEF. = 'L' ( # 32 )
retlw (_A+_B+_C +_E+_F )^SSEG_XORMASK ; ABC.EF. = 'N' ( # 33 )
retlw ( _C+_D+_E+_F )^SSEG_XORMASK ; ..CDEF. = 'V' ( # 34 )
retlw ( _D +_G)^SSEG_XORMASK ; ...D..G = '=' ( # 35 )
;--------------------------------------------------------------------------
; Table to control which 7-segment display is enabled. Displays are usually
; COMMON CATHODE (variants 1+2) so pulled low to enable.
; For DISP_VARIANT=3 (COMMON ANODE), the digit-driving pattern is inverted.
; Input: W = 0 means the MOST SIGNIFICANT DIGIT (the leftmost one), etc.
; Result: VALUE to be written to ENABLE_PORT to activate the digit
;--------------------------------------------------------------------------
Digit2MuxValue: ;
addwf PCL,f ; caution: this is 'PCL' only, not 'PC'
; Note: If the program counter is affected, a command requires to instruction cycles (=8 osc cycles)
#if (DISP_VARIANT==1) ; muliplexer values for DISPLAY VARIANT #1 :
retlw b'11110111' ; most significant digit is on PA3 (!)
retlw b'11111110' ; next less significant dig. on PA0 (!)
retlw b'11111011' ; next less significant dig. on PA2 (!)
retlw b'11111101' ; 4th (sometimes the last) digit PA1 (!)
retlw b'11111111' ; 5th (OPTIONAL) least significant digit = NOT (PA3+PA2+PA1+PA0)
#endif ; DISPLAY VARIANT #1
#if (DISP_VARIANT==2) ; muliplexer values for DISPLAY VARIANT #2 (5 digits, COMMON CATHODE) :
retlw b'11110111' ; most significant digit is on PA3 (!)
retlw b'11111011' ; next less significant dig. on PA2 (!!)
retlw b'11111110' ; next less significant dig. on PA0 (!!)
retlw b'11111101' ; 4th (sometimes the last) digit PA1 (!)
retlw b'11111111' ; 5th (OPTIONAL) least significant digit = NOT (PA3+PA2+PA1+PA0)
#endif ; DISPLAY VARIANT #2
#if (DISP_VARIANT==3) ; muliplexer values for DISPLAY VARIANT #3 (5 digits, COMMON ANODE) :
; Unused bits (b7..b4) are left HIGH as above .
retlw b'11111000' ; most significant digit is on PA3 (!)
retlw b'11110100' ; next less significant dig. on PA2 (!!)
retlw b'11110001' ; next less significant dig. on PA0 (!!)
retlw b'11110010' ; 4th (sometimes the last) digit PA1 (!)
retlw b'11110000' ; 5th (OPTIONAL) least significant digit = NOT (PA3+PA2+PA1+PA0)
#endif ; DISPLAY VARIANT #2
;--------------------------------------------------------------------------
; Powers-of-ten table (32 bits, most significant byte first)
; Based on an idea by James Hutchby (MadLab, 1996) .
; Modified for 32-bit arithmetic by Wolfgang Buescher (2004).
;--------------------------------------------------------------------------
TensTable addwf PCL,f
cquad .10000000 ; 10 million is sufficient for the counter itself
cquad .1000000
cquad .100000
cquad .10000
cquad .1000
cquad .100
cquad .10
cquad .1
;--------------------------------------------------------------------------
; DISPLAY jump table for programming mode .
; Loads the display-strings like "quit" etc into the display latches.
; Input parameter: menu_index (0 .. MI_INDEX_MAX)
; Output placed in display0..display3
;
;--------------------------------------------------------------------------
PMDisplay:
movfw menu_index ; load menu index into W register
addwf PCL, f ; add W to lower part of program counter (computed jump)
goto PmDisp_Quit ; show "quit" (quit programming mode)
goto PmDisp_PSave; show "PSave"(power-saving mode on/off)
goto PmDisp_Add ; show "add " (add frequency offset)
goto PmDisp_Sub ; show "sub " (subtract frequency offset)
goto PmDisp_Zero ; show "Zero" (set frequency offset to zero)
goto PmDisp_StIF ; show "StdIF" (select standard IF from table)
goto PmDisp_IF_1 ; show 1st standard IF from table
goto PmDisp_IF_2 ; show 2nd standard IF from table
goto PmDisp_IF_3 ; show 3rd standard IF from table
goto PmDisp_IF_4 ; show 4th standard IF from table
goto PmDisp_IF_5 ; show 5th standard IF from table
goto PmDisp_Quit ; show "quit" (quit STANDARD IF menu)
; Add more display strings here if needed !
;--------------------------------------------------------------------------
; EXECUTION jump table for programming mode .
; Executes the commands "quit", "psave", "add", "sub", "zero", etc.
; Input parameter: menu_index (0 .. MI_INDEX_MAX)
;--------------------------------------------------------------------------
PMExecute: ; Execute the function belonging to menu_index
movfw menu_index ; load menu index into W register
addwf PCL, f ; add W to lower part of program counter (computed jump)
goto PmExec_Quit ; quit programming mode
goto PmExec_PSave; turn power-saving mode on/off
goto PmExec_Add ; add frequency offset from now on
goto PmExec_Sub ; subtract frequency offset from now on
goto PmExec_Zero ; set frequency offset to zero
goto PmExec_StIF ; switch to "Standard IF selection mode"
goto PmExec_SelIF ; select 1st standard IF from table
goto PmExec_SelIF ; select 2nd standard IF from table
goto PmExec_SelIF ; select 3rd standard IF from table
goto PmExec_SelIF ; select 4th standard IF from table
goto PmExec_Quit ; quit STANDARD IF menu
; Add more jumps here if needed !
;**************************************************************************
; *
; Procedures *
; *
;**************************************************************************
;--------------------------------------------------------------------------
; Configure the prescaler for TIMER 0 in the PIC's OPTION register .
;--------------------------------------------------------------------------
; Description of the OPTION register, from the PIC16F628 data sheet:
; bit 7: RBPU: PORTB Pull-up Enable bit
; 1 = PORTB pull-ups are disabled
; 0 = PORTB pull-ups are enabled by individual port latch values
; bit 6: INTEDG: Interrupt Edge Select bit
; 1 = Interrupt on rising edge of RB0/INT pin
; 0 = Interrupt on falling edge of RB0/INT pin
; bit 5: T0CS: TMR0 Clock Source Select bit
; 1 = Transition on RA4/T0CKI pin
; 0 = Internal instruction cycle clock (CLKOUT)
; bit 4: T0SE: TMR0 Source Edge Select bit
; 1 = Increment on high-to-low transition on RA4/T0CKI pin
; 0 = Increment on low-to-high transition on RA4/T0CKI pin
; bit 3: PSA: Prescaler Assignment bit
; 1 = Prescaler is assigned to the WDT
; 0 = Prescaler is assigned to the Timer0 module
; bit 2-0: PS2:PS0: Prescaler Rate Select bits, here shown for TMR0 :
; 000 = 1 : 2
; ... 111 = 1 : 256
; Note: to count EVERY pulse (1 : 1) with TMR0, the prescaler
; must be assigned to the WATCHDOG TIMER (WDT) !
; Some examples (for the OPTION register, parameter in W for SetPrescaler):
PSC_DIV_BY_2 equ b'00100000' ; let prescaler divide TMR0 by two
PSC_DIV_BY_4 equ b'00100001' ; let prescaler divide TMR0 by 4
PSC_DIV_BY_8 equ b'00100010' ; let prescaler divide TMR0 by 8
PSC_DIV_BY_16 equ b'00100011' ; let prescaler divide TMR0 by 16
PSC_DIV_BY_32 equ b'00100100' ; let prescaler divide TMR0 by 32
PSC_DIV_BY_64 equ b'00100101' ; let prescaler divide TMR0 by 64
PSC_DIV_BY_128 equ b'00100110' ; let prescaler divide TMR0 by 128
PSC_DIV_BY_256 equ b'00100111' ; let prescaler divide TMR0 by 256
SetPrescaler: ; copy W into OPTION register, avoid watchdog trouble
clrwdt ; recommended by Microchip ("switching prescaler assignment")
errorlevel -302 ; Turn off banking message for the next few instructions..
bsf STATUS, RP0 ;! setting RP0 enables access to OPTION reg
; option register is in bank1. i know. thanks for the warning.
movwf OPTION_REG ;! ex: "option" command (yucc)
bcf STATUS, RP0 ;! clearing RP0 for normal register access
errorlevel +302 ; Enable banking message again
retlw 0
PrescalerOff: ; turn the prescaler for TMR0 "off"
; (actually done by assigning the prescaler to the watchdog timer)
clrwdt ; clear watchdog timer
clrf TMR0 ; clear timer 0 AND PRESCALER(!)
errorlevel -302 ; Turn off banking message for the next few instructions..
bsf STATUS, RP0 ;! setting RP0 enables access to OPTION reg
; option register is in bank1. i know. thanks for the warning.
movlw b'00100111' ;! recommended by Microchip when
;! changing prescaler assignment from TMR0 to WDT
movwf OPTION_REG ;! ex: "option" command (yucc)
clrwdt ;! clear watchdog again
movlw b'00101111' ;! bit 3 set means PS assigned to WDT now
movwf OPTION_REG ;! ex: "option" command (yucc)
bcf STATUS, RP0 ;! clearing RP0 for normal register access
errorlevel +302 ; Enable banking message again
retlw 0
;--------------------------------------------------------------------------
; Power-saving subroutine: Puts the PIC to sleep for ROUGHLY 100 milliseconds .
; - crystal oscillator turned OFF during this phase
; - only the internal RC-oscillator for the watchdog keeps running
; - expiration of watchdog during sleep does NOT reset the PIC,
; only wakes it up again so normal operation may resume
; - LED display will be off during this time
;--------------------------------------------------------------------------
Sleep150ms: ; go to sleep for approx. 150 milliseconds, and then RETURN (no reset)
; Details on the PIC's watchdog timer (from PIC16F628 datasheet) :
; > The WDT has a nominal timeout period of 18 ms (with
; > no prescaler). The timeout periods vary with temperature,
; > VDD and process variations from part to part (see
; > DC specs).
; > The Watchdog Timer is a free running on-chip RC oscillator which does
; > not require any external components. This RC oscillator is separate
; > from the ER oscillator of the CLKIN pin. That means that the WDT will run,
; > even if the clock on the OSC1 and OSC2 pins of the device has been stopped,
; > for example, by execution of a SLEEP instruction.
; > During normal operation, a WDT timeout generates a device RESET.
; > If the device is in SLEEP mode, a WDT timeout causes the device to wake-up
; > and continue with normal operation.
; > The WDT can be permanently disabled by programming the configuration bit
; > WDTE as clear .
; In other words, to use the watchdog-timer for "temporary sleep" here ,
; it must be ENABLED in the configuration word when programming the PIC.
; (because its not possible to turn it on via software if it's not on).
; But once the watchdog timer is ON, it must be FED periodically otherwise
; it will reset the PIC during normal operation !
; Here (in the frequency counter), the prescaler remains assigned to timer0
; so the watchdog interval is ~ 18 milliseconds (+/-, RC-oscillator) .
; > The CLRWDT and SLEEP instructions clear the WDT and the postscaler,
; > if assigned to the WDT, and prevent it from timing out and generating
; > a device RESET. The TO bit in the STATUS register will be cleared upon
; > a Watchdog Timer timeout.
#if(COMMON_CATHODE) ; display with COMMON CATHODE :
movlw 0x00 ; segment drivers LOW to turn off
#else ; not COMMON CATHODE but COMMON ANODE:
movlw 0xFF ; segment drivers HIGH to turn off
#endif
#ifndef RS232_OUT
movwf LEDS_PORT ; turn LED segments off
#else
#ifdef NON_INVERTING_RS232_OUT
bcf LEDS_PORT, 2 ; RS232 on RB2 to zero
#else
bsf LEDS_PORT, 2 ; RS232 on RB2 to one
#endif
; NON_INVERTING_RS232_OUT
#endif
; RS232_OUT
; Note: The global interrupt-enable flag (GIE) is off in this application !
; To avoid unintended wake-up on 'interrupt' (port level change),
; disable all interrupt-SOURCES: Clear T0IE,INTE,RBIE,PEIE too :
clrf INTCON ; disable all interrupts during SLEEP mode
clrwdt ; clear watchdog timer
clrf TMR0 ; clear timer 0 AND PRESCALER(!)
errorlevel -302 ; Turn off banking message for the next few instructions..
bsf STATUS, RP0 ;! setting RP0 enables access to OPTION reg
; option register is in bank1. i know. thanks for the warning.
movlw b'00101011' ;! assign PS to WDT; divide by 8 FOR WDT(!)
movwf OPTION_REG ;! ex: "option" command (yucc)
bcf STATUS, RP0 ;! clearing RP0 for normal register access
errorlevel +302 ; Enable banking message again
sleep ; sleep for approx 18 ms (one watchdog interval)
; The SLEEP command clears the Watchdog Timer and stops the main oscillator.
; Only the internal watchdog timer keeps running.
; The WDT is is also cleared when the device wakes-up from SLEEP,
; regardless of the source of wake-up, so no need for 'clrwdt' here !
nop ; arrived here, slept for ~ 8 times 18 milliseconds
return ; end Sleep150ms
;--------------------------------------------------------------------------
; Convert a character into LEDs data for the 7-segment displays, fed with
; the character in w. Bit 7 set means 'decimal point AFTER this digit' .
;--------------------------------------------------------------------------
; WAS print 5 digits with MHz and kHz indication/.
conv macro display ; macro for duplicate code
movwf display ; save decimal point bit (msb)
andlw 7fh ; mask bit
#ifndef RS232_OUT
call Digit2SevenSeg ; convert digit into 7-segment-code via table
btfsc display,7 ; check bit 7 = decimal point ?
#if(COMMON_CATHODE)
iorlw 1<<DPPOINT_BIT ; include decimal point if bit 7 set (bitwise OR)
#else ; not COMMON CATHODE but COMMON ANODE: decimal point must be 'AND'ed to pattern:
andlw (1<<DPPOINT_BIT)^0xFF ; include decimal point if bit 7 set (bitwise AND)
#endif
movwf display ; set display data register
#else ; RS232_OUT
#ifdef RS232_PRINT_FIELD_3
movwf temp ; save w
; BLANK, displayed as 'C', indicates no input, going to skip printing those.
bcf print_flags, NO_INPUT_FLAG
; no jumps in macro
movlw BLANK
subwf temp, w
btfsc STATUS, Z
bsf print_flags, NO_INPUT_FLAG
; get back value to print
movfw temp
; test if anything other then zero, if so display it
btfss print_flags, NO_INPUT_FLAG
call tx_digit_in_w
; test for decimal point
btfsc display, 7
call tx_dot
#endif ; RS232_PRINT_FIELD_3
#endif
endm
; 7 segment out
conv_char0: ; display digit #0 (leftmost, or MOST SIGNIFICANT digit)
conv display0
retlw 0
conv_char1: ; display #1
conv display1
retlw 0
conv_char2: ; display #2
conv display2
retlw 0
conv_char3: ; display #3
conv display3
retlw 0
conv_char4: ; display #4 (rightmost, or LEAST SIGNIFICANT digit, "ones")
conv display4
#ifdef RS232_OUT
#ifdef RS232_PRINT_FIELD_3
; print a space
movlw ' '
call tx_w
; test if to print kHz or MHz
btfsc print_flags, KILOHERTZ_FLAG
goto print_kilo
; print 'M'
movlw 'M'
call tx_w
goto print_hertz
print_kilo:
movlw 'k'
call tx_w
; say Hz
print_hertz:
movlw 'H'
call tx_w
movlw 'z'
call tx_w
#endif RS232_PRINT_FIELD_3
; send a CR LF
movlw D'10'
call tx_w
movlw D'13'
call tx_w
#endif ; RS232_OUT
retlw 0
; 7 segment out
;--------------------------------------------------------------------------
; Fill the 5-digit display latch with blank characters
;--------------------------------------------------------------------------
ClearDisplay:
movlw BLANK_PATTERN
movwf display0
movwf display1
movwf display2
movwf display3
movwf display4
retlw 0
;--------------------------------------------------------------------------
; Save a single Byte in the PIC's Data-EEPROM.
; Input parameters:
; INDF = *FSR contains byte to be written (was once EEDATA)
; w contains EEPROM address offset (i.e. "destination index")
;
;--------------------------------------------------------------------------
; write to EEPROM data memory as explained in the 16F628 data sheet.
; EEDATA and EEADR must have been set before calling this subroutine
; (optimized for the keyer-state-machine).
; CAUTION : What the lousy datasheet DS40300B wont tell you:
; The example given there for the 16F628 is WRONG !
; All EEPROM regs are in BANK1 for the 16F628.
; In the PIC16F84, some were in BANK0 others in BANK1..
; In the PIC16F628, things are much different... all EEPROM regs are in BANK1 !
SaveInEEPROM: ; save "INDF" = *FSR in EEPROM[<w>]
bcf INTCON, GIE ; disable INTs
errorlevel -302 ; Turn off banking message for the next few instructions..
bsf STATUS, RP0 ;!; Bank1 for "EEADR" access, PIC16F628 ONLY (not F84)
movwf EEADR ;!; write into EEPROM address register (BANK1 !!)
bcf STATUS, RP0 ;!; Bank0 to read "bStorageData"
movfw INDF ; ; w := *FSR (read source data from BANK 0)
bsf STATUS, RP0 ;!; Bank1 for "EEDATA" access, PIC16F628 ONLY (not F84)
movwf EEDATA ;!; EEDATA(in BANK1) := w (BANK1; F628 only, NOT F84 !!!)
bsf EECON1, WREN ;!; set WRite ENable
bcf INTCON, GIE ;!; Is this REALLY required as in DS40300B Example 13-2 ?
movlw 055h ;!;
movwf EECON2 ;!; write 55h
movlw 0AAh ;!;
movwf EECON2 ;!; write AAh
bsf EECON1, WR ;!; set WR bit, begin write
; wait until write access to the EEPROM is complete.
SaveEW: btfsc EECON1, WR ;!; WR is cleared after completion of write
goto SaveEW ;!; WR=1, write access not finished yet
; Arrived here: the EEPROM write is ready
bcf EECON1, WREN ;!; disable further WRites
bcf STATUS, RP0 ;!; Bank0 for normal access
errorlevel +302 ; Enable banking message again
; bsf INTCON, GIE ; enable INTs ? NOT IN THIS APPLICATION !
retlw 0 ; end SaveInEEPROM
;--------------------------------------------------------------------------
; Read a single Byte from the PIC's Data-EEPROM.
; Input parameters:
; w contains EEPROM address offset (i.e. "source index")
; will *NOT* be modified to simplify block-read .
; FSR points to the memory location where the byte shall be placed.
;
; Result:
; INDF = *FSR returns the read byte
;--------------------------------------------------------------------------
; Caution: EEDATA and EEADR have been moved from Bank0(16F84) to Bank1(16F628)
; and the example from the datasheet telling you to switch to
; bank0 to access EEDATA is rubbish (DS40300B page 93 example 13-1).
EEPROM_ReadByte: ; read ONE byte from the PIC's data EEPROM
movwf bTemp ; save W
bcf INTCON, GIE ; disable INTs
errorlevel -302 ; Turn off banking message for the next few instructions..
bsf STATUS, RP0 ; Bank1 for ***ALL*** EEPROM registers in 16F628 (!)
movwf EEADR ;! write into EEPROM address register
bsf EECON1, RD ;! set "Read"-Flag for EEPROM
; why is EECON1.RD not cleared in MPLAB-sim ?!?
movf EEDATA, w ;! read byte from EEPROM latch
bcf STATUS, RP0 ;! normal access to Bank0
errorlevel +302 ; Enable banking message again
; bsf INTCON, GIE ; re-enable interrupts ? NOT IN THIS APPLICATION !
movwf INDF ; place result in *FSR
movfw bTemp ; restore W
return ; back to caller
; end EEPROM_ReadByte
EEPROM_Read4Byte: ; read FOUR bytes from the PIC's data EEPROM.
; Input parameters:
; w contains EEPROM address offset (i.e. "source index")
; will *NOT* be modified to simplify block-read .
; FSR points to the memory location where the byte shall be placed.
call EEPROM_ReadByte ; *FSR = EEPROM[w] (usually bits 31..24)
addlw 1 ; next source address
incf FSR , f ; next destination address
call EEPROM_ReadByte ; *FSR = EEPROM[w] (usually bits 23..16)
addlw 1 ; next source address
incf FSR , f ; next destination address
call EEPROM_ReadByte ; *FSR = EEPROM[w] (usually bits 15..8)
addlw 1 ; next source address
incf FSR , f ; next destination address
goto EEPROM_ReadByte ; *FSR = EEPROM[w] (usually bits 7..0)
; end EEPROM_Read4Byte
;--------------------------------------------------------------------------
; Count pulses, fed with the number of loop iterations for the gate time .
; WHILE counting, the multiplexed LED display is updated .
; Watchdog is fed in this loop !
; Input: Count of gate-time-loops in 'gatecnt_hi'+'gatecnt_lo' (16 bit).
; Returns: The number of pulses in 'freq' (clock cycles in [])
;--------------------------------------------------------------------------
count_pulses:
clrf freq_hi ; clear pulse counter (bits 31..24)
clrf freq_mh ; bits 23..16
clrf freq_ml ; bits 16..8
clrf freq_lo ; bits 7..0
clrf timer0_old ; 'old' value of timer0 to detect toggling MSB
clrf TMR0 ; timer register (PIC's hardware timer, 8 bit)
nop ; 2 instruction cycle delay
nop ; after writing to TMR0 (MPLAB-SIM: set breakpoint + clear stopwatch here)
; --------------- start of critial timing loop >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; The following timing loop must take a well-defined time in total per
; iteration, usually 50 (or 20) microseconds, which can be precisely achieved
; with a 4-MHz-crystal (or 20 MHz for variant 2+3) .
; This gives a basic delay for the frequency counter's gate time .
; The frequency at the input of TIMER 0 (not the prescaler)
; can not exceed f_crystal / 4,
; and every HIGH->LOW transition of bit7 in TIMER0 must be polled here.
; This is safe because ..
; Variant 1: With a 4-MHz-crystal, Timer0 can count up to 1 MHz input,
; MSB toggles every (128/1MHz) = 128 us, polled every 50us -> ok.
; Variant 2: With a 20-MHz-crystal, Timer0 can count up to 4 (not 5?!) MHz input,
; MSB toggles every (128/4MHz) = 32 us, polled every 20us -> ok.
; The numbers in square brackets below are the INSTRUCTION NUMBER within the loop.
; (not the count of oscillator cycles for a single command, which is always 4).
; These values can be checked with the "Stopwatch" function in MPLAB-SIM.
; The goal is to let this loop take EXACTLY <TIME> microseconds (50us or 20us).
count1 movfw disp_index ; [1] get the current digit number (disp_index = 0..4)
call Digit2MuxValue ; [2,3,4,5,6,7] display (6 commands including call+retlw)
movwf bTemp ; [8] save the bit pattern for the multiplexer port
movlw display0 ; [9] get the LED display data for the current digit...
addwf disp_index,w ; [10] add current digit number to address of LED data
movwf FSR ; [11] move address into the PIC's poor 'data pointer'
#ifndef RS232_OUT
movfw INDF ; [12] w := *(FSR) use indirection register to read from table
movwf LEDS_PORT ; [13] set the LED segments
#else
; leave port B alone, so as not to disturb RS232 software out.
nop
nop
#endif
movfw bTemp ; [14] get the mupliplexer pattern (hurry, hurry !)
movwf ENABLE_PORT ; [15] set the LED multiplexer
incf disp_timer,f ; [16] increment display-multiplex timer
btfsc disp_timer,6 ; [17] (6-bit prescaler)
incf disp_index,f ; [18] next display if rolled over
bcf disp_timer,6 ; [19] limit disp_timer to 6 bits (!)
movfw disp_index ; [20] limit display index to 0...4
sublw .4 ; [21] subtract #4 - W register -> C=0(!) if result negative (W>4)
btfss STATUS,C ; [22] skip next instruction if C=1 (#4-W >= 0)
clrf disp_index ; [23] if C=0 (disp_index>4) then disp_index=0
; the following fragments of code always take the same number of clock
; cycles to execute, irrespective of whether the skips take place or not .
; Here still in 'count_pulses'.
movfw TMR0 ; [24] read least significant byte of
movwf freq_lo ; [25] pulse counter (bits 7..0)
movlw 1 ; [26] determine if timer 0 has rolled
btfss timer0_old,7 ; [27] over (rolled over if msb was
clrw ; [28] previously set and now isn't)
btfsc freq_lo,7 ; [29]
clrw ; [30]
addwf freq_ml,f ; [31] increment high bytes of pulse counter
skpnc ; [32] if low byte rolled over
incf freq_mh,f ; [33] (mh = "medium high byte" of counter)
; NOTE: we are not modifying freq_hi here !
; Bits 31..24 may be used later when multiplying with some factor
; (2^n) to compensate for the ASYNCHRON PRESCALER !
btfsc freq_mh,7 ; [34] overflow (freq > 7fffffh) ?
goto count3 ; [35] branch if yes
movfw freq_lo ; [36] save previous value from timer 0
movwf timer0_old ; [37]
tstf gatecnt_lo ; [38] check inner gate-time counter, LOW byte
skpnz ; [39] only decrement h-byte if l-byte zero
decf gatecnt_hi,f ; [40] decrement gate-time counter, HIGH byte
decf gatecnt_lo,f ; [41] always decrement gate-time counter, LOW byte
#if (DISP_VARIANT==1) ; only 50 instruction cycles per loop in DISPLAY VARIANT 1 (f_xtal=4 MHz, t_loop=50us)
; Got some instruction cycles left ? Insert a few NOPs to bring to total loop time to 50us.
clrwdt ; [42] (ex: nop, but since 2006-05-28 the dog must be fed !)
nop ; [43]
nop ; [44]
nop ; [45] ugh, what a waste of precious CPU power ;-)
movfw gatecnt_hi ; [46] counter = 0 ?
iorwf gatecnt_lo,w ; [47]
skpz ; [48]
goto count1 ; [49,50] goto always takes TWO instruction cycles
#else ; For VARIANTS 2+3 : 100 instruction cycles per loop
; (f_xtal=20 MHz, t_loop=20us, t_instr=4/20MHz=0.2us)
; Some time may be used for a nice software-based PULSE WIDTH MODULATION
; of the display intensity ... or other goodies/gimmicks one fine day !
clrwdt ; [42] (ex: nop, but since 2006-05-28 the dog must be fed !)
movlw .12 ; [43] load additional delay loops (X=12, see below) into W
WasteT1: addlw 0xFF ; [44, 48, .. ]
btfss STATUS, Z ; [45, 49, .. ] eats 4(!) INSTRUCTION CYCLES per loop
goto WasteT1 ; [46+47,50+51, .. ]
; Check this with MPLAB-SIM: here, after loop: [43 + 4*X], with X=12: [91]
nop ; [91]
nop ; [92]
nop ; [93]
nop ; [94]
nop ; [95]
movfw gatecnt_hi ; [96] counter = 0 ?
iorwf gatecnt_lo,w ; [97]
skpz ; [98]
goto count1 ; [99,50] goto always takes TWO instruction cycles
#endif ; variant 1 or variant 2/3 ?
; <<<<<<<<<<<<<<<<<<<<<<<< end of timing loop -----------------------------
movfw TMR0 ; get final value from timer 0
movwf freq_lo
movlw 1 ; determine if timer 0 has rolled
btfss timer0_old,7 ; over (rolled over if msb was
clrw ; previously set and now isn't)
btfsc freq_lo,7
clrw
addwf freq_ml,f ; increment high bytes of pulse
skpnc ; counter if low byte rolled
incf freq_mh,f ; over
count3 retlw 0
; end of routine 'count_pulses'. Result now in freq_lo..freq_hi.
#ifdef RS232_OUT
tx_dot:
movlw '.'
call tx_w
return
; send_one_char
; the actual RS232 transmission routine, half-duplex, no-flow-control.
; See AN510 for an explanation
tx_digit_in_w:
addlw '0' ; zero
tx_w:
banksel 0
; return
; movlw 'A'
movwf tx_reg ; move W (char to send) to TXReg
movlw 0x08
movwf bit_count ; send 8 bits
; send start bit
#ifdef NON_INVERTING_RS232_OUT
bsf RS232_PORT, RS232_BIT
#else
bcf RS232_PORT, RS232_BIT
#endif
nop
nop
nop
nop
call bit_delay
; send data bits
send_next_bit:
bcf STATUS, C
rrf tx_reg, 1 ; rotate TXReg
btfsc STATUS, C
goto set_tx
clear_tx:
nop ; to get equal set/clear times
#ifdef NON_INVERTING_RS232_OUT
bsf RS232_PORT, RS232_BIT
#else
bcf RS232_PORT, RS232_BIT
#endif
goto ready_tx
set_tx:
#ifdef NON_INVERTING_RS232_OUT
bcf RS232_PORT, RS232_BIT
#else
bsf RS232_PORT, RS232_BIT
#endif
goto ready_tx
ready_tx:
call bit_delay
decfsz bit_count,1 ; decrement bit counter (8..0)
goto send_next_bit ; loop for next data bit
nop
nop
nop
nop
nop
; send first stop bit
#ifdef NON_INVERTING_RS232_OUT
bcf RS232_PORT, RS232_BIT
#else
bsf RS232_PORT, RS232_BIT
#endif
call bit_delay
; send second stop bit
; call bit_delay
return
; This routine is calibrated with BIT_DELAY to 104 us, that makes BAUD_DIVIDER 1 for 9600 Bd, 2 for 4800 Bd, 4 for 2400 Bd, 8 for 1200 Bd, 16 for 600 Bd, 32 for 300 Bd, 64 for 150 Bd, and 128 for 75 Bd.
bit_delay:
; prevent watchdog from interrupting serial com
clrwdt ; should be called on a regular basis
; Multiply bit delay for lower baudrates.
movlw BAUD_DIVIDER
movwf baud_divider
baud_divider_loop:
; this is the delay of about 104 uS for 9600 Bd
movlw BIT_DELAY ; move baud delay constant to W
movwf delay_counter ; initialize delay counter
us100_delay_loop:
decfsz delay_counter ; decrement delay counter
goto us100_delay_loop
decfsz baud_divider
goto baud_divider_loop
return
#endif
; RS232_OUT
;--------------------------------------------------------------------------
; Convert *FSR (32 bit) into BCD and show it on the display .
; Input : INDF = *FSR, 32-bit integer.
; Bad side effect : CONTENTS OF <freq> will be lost !!
;--------------------------------------------------------------------------
ShowInt32_FSR ; Convert <*FSR> (32 bit integer) to 8 BCD-digits ...
movfw INDF ; W := *FSR , load LOW byte
incf FSR , f ; FSR := FSR + 1
movwf freq ; freq.hi := W
movfw INDF ; W := *FSR , load MIDDLE LOW byte
incf FSR , f ; FSR := FSR + 1
movwf freq+1 ; freq.mh := W
movfw INDF ; W := *FSR , load MIDDLE HIGH byte
incf FSR , f ; FSR := FSR + 1
movwf freq+2 ; freq.ml := W
movfw INDF ; W := *FSR , load HIGH byte
incf FSR , f ; FSR := FSR + 1
movwf freq+3 ; freq.lo := W
; continue with CvtAndDisplayFreq !
;--------------------------------------------------------------------------
; Convert <freq> into BCD and show it on the display .
; Input : freq, 32-bit integer. CONTENTS OF <freq> will be lost !!
;--------------------------------------------------------------------------
CvtAndDisplayFreq ; Convert <freq>(32 bit integer) to 8 BCD-digits ...
clrf tens_index ; initialise the table index
movlw digits ; initialise the indirection register
movwf FSR ; ( FSR="pointer"; *FSR=INDF)
conv1 ; Loop for ALL POWERS OF TEN in the lookup table..
clrwdt ; feed the watchdog (may stay a bit longer)
movfw tens_index ; fetch the next power of ten
call TensTable ; (32 bits) from the lookup table
movwf divi+0 ; and store in divi
incf tens_index , f ; this was the HIGH byte
movfw tens_index
call TensTable
movwf divi+1
incf tens_index , f ; this was the MIDDLE-HIGH byte
movfw tens_index
call TensTable
movwf divi+2
incf tens_index , f ; this was the MIDDLE-LOW byte
movfw tens_index
call TensTable
movwf divi+3
incf tens_index , f ; and this was the LOW-byte of a power of ten
; ex: clrf 0 ; clear the decimal digit .. but address ZERO is called 'INDF' these days !
clrf INDF ; *FSR = 0
conv2 ; Loop to repeatedly subtract divi from freq (32-bit subtract)
; until underflow while incrementing the decimal digit.
sub32 freq,divi ; freq := freq - divi (with divi = 10 power N)
bnc conv3 ;
incf INDF , f ; The RESULT will be written back to freq,
goto conv2 ; in other words 'freq' will be lost !
conv3 add32 freq,divi ; freq := freq+divi; ready for next digit
incf FSR , f ; step to next decimal digit
movlw 8*4 ; 8 x 4-byte entries in TensTable
subwf tens_index,w
bnz conv1 ; loop until end of table
;--------------------------------------------------------------------------
; displays the frequency in decimal
;--------------------------------------------------------------------------
display_freq:
; Display the decimal digits according to the following rules
; 000000A => "0.00A"
; 00000AB => "0.0AB"
; 0000ABC => "0.ABC"
; 000ABCD => "A.BCD"
; 00ABCDE => "AB.CD"
; 0ABCDEF => "ABC.D"
; ABCDEFG => "ABCD."
; Modified a lot by WoBu to display kHz as well as MHz :
; If the decimal point means kHz, it flashes.
; If it means MHz, it is on permanently.
; 24 bit unsigned integer could count up to 16777216 (16 mio, slightly over 7 digits)
; which was not enough for a 50 MHz counter, so switched to 32-bit arithmetic .
;
#ifdef RS232_OUT
#ifdef RS232_PRINT_FIELD_1
; WAS print 8 digits as one field for parsin gby user programs, no leading zero suppression
; print_value simple
movlw digits
movwf FSR
movfw INDF
call tx_digit_in_w
incf FSR
movfw INDF
call tx_digit_in_w
incf FSR
movfw INDF
call tx_digit_in_w
incf FSR
movfw INDF
call tx_digit_in_w
incf FSR
movfw INDF
call tx_digit_in_w
incf FSR
movfw INDF
call tx_digit_in_w
incf FSR
movfw INDF
call tx_digit_in_w
incf FSR
movfw INDF
call tx_digit_in_w
; print 2 spaces
movlw ' '
call tx_w
movlw ' '
call tx_w
#endif ; RS232_PRINT_FIELD_1
#ifdef RS232_PRINT_FIELD_2
; print value in Hz, with leading zero surpression
; print_value: thoudands separated by commas
bsf print_flags, ZERO_SUPPRESSION_FLAG
movlw digits
movwf FSR
tstf INDF
bz pri_1000000
pri_10000000:
bcf print_flags, ZERO_SUPPRESSION_FLAG
movfw INDF
tstf INDF
bz pri_100000
call tx_digit_in_w
pri_1000000:
incf FSR
; test if zero supression active
btfss print_flags, ZERO_SUPPRESSION_FLAG
goto pri_a
tstf INDF
bz pri_100000
pri_a:
bcf print_flags, ZERO_SUPPRESSION_FLAG
movfw INDF
call tx_digit_in_w
movlw ','
call tx_w
pri_100000:
incf FSR
btfss print_flags, ZERO_SUPPRESSION_FLAG
goto pri_b
tstf INDF
bz pri_10000
pri_b:
bcf print_flags, ZERO_SUPPRESSION_FLAG
movfw INDF
call tx_digit_in_w
pri_10000:
incf FSR
btfss print_flags, ZERO_SUPPRESSION_FLAG
goto pri_c
tstf INDF
bz pri_1000
pri_c:
bcf print_flags, ZERO_SUPPRESSION_FLAG
movfw INDF
call tx_digit_in_w
pri_1000:
incf FSR
btfss print_flags, ZERO_SUPPRESSION_FLAG
goto pri_d
tstf INDF
bz pri_100
pri_d:
bcf print_flags, ZERO_SUPPRESSION_FLAG
movfw INDF
call tx_digit_in_w
movlw ','
call tx_w
pri_100:
incf FSR
btfss print_flags, ZERO_SUPPRESSION_FLAG
goto pri_e
tstf INDF
bz pri_10
pri_e:
bcf print_flags, ZERO_SUPPRESSION_FLAG
movfw INDF
call tx_digit_in_w
pri_10:
incf FSR
btfss print_flags, ZERO_SUPPRESSION_FLAG
goto pri_f
tstf INDF
bz pri_1
pri_f:
bcf print_flags, ZERO_SUPPRESSION_FLAG
movfw INDF
call tx_digit_in_w
pri_1:
incf FSR
movfw INDF
call tx_digit_in_w
pri_space:
; space
movlw ' '
call tx_w
; Hz
movlw 'H'
call tx_w
movlw 'z'
call tx_w
; print 2 spaces
movlw ' '
call tx_w
movlw ' '
call tx_w
#endif ; RS232_PRINT_FIELD_2
#endif ; RS232_OUT
; Display routine for frequencies up to "99.99 MHz" (theoretical):
; (do NOT insert the decimal point yet,
; it would disturb the blanking of LEADING zeroes )
movlw digits ; find the first significant digit..
movwf FSR ; .. by stepping over leading zeroes
tstf INDF ; INDF = *(FSR) in "C" syntax, FSR points to 'digits'
bnz displ_MHz ; 10-MHz-digit non-zero, show frequency in MHz
incf FSR , f ; otherwise skip 1st digit (the 10-MHz place)
tstf INDF
bnz displ_MHz ; 1-MHz-digit non-zero, show frequency in MHz
incf FSR , f ; otherwise skip 2nd digit (the 1-MHz place)
tstf INDF
bnz displ_kHz ; 100-kHz-digit non-zero, show frequency in kHz (XXX.X)
incf FSR , f ; otherwise skip 3rd digit (the 100-kHz place)
tstf INDF
bnz displ_kHz ; 10-kHz-digit non-zero, show frequency in kHz (XX.XX)
incf FSR , f ; Otherwise show digits 5,6,7,8 (there are EIGHT digits)
; show all these frequencies with flashing kHz-point (X.XXX)
displ_kHz: ; insert a BLINKING POINT to indicate the kilohertz-digit
#ifndef RS232_OUT
btfsc blinker, 0 ; check the blink flag (bit 0) for the kHz-point
#endif
; RS232_OUT
; in RS232_OUT we always have a dot if kHz (non blinking).
bsf digit_4, 7 ; set the decimal point indicating the frequency in kHz .
bsf print_flags, KILOHERTZ_FLAG
goto display
displ_MHz: ; insert a BLINKING POINT to indicate the kilohertz-digit
bsf digit_1, 7 ; set the decimal point indicating the frequency in MHz .
bcf print_flags, KILOHERTZ_FLAG
display: ; Show the FIVE digits beginning at INDF = *(FSR) on the LED display...
movfw INDF ; convert the four digits to
call conv_char0 ; LED display data
incf FSR , f ; increment pointer to next digit
movfw INDF ; w = *(FSR)
call conv_char1 ; second visible digit
incf FSR , f
movfw INDF
call conv_char2 ; third visible digit
incf FSR , f
movfw INDF
call conv_char3 ; fourth visible digit
incf FSR , f
movfw INDF
goto conv_char4 ; convert fifth visible digit AND RETURN
; end of routine "CvtAndDisplayFreq"
;--------------------------------------------------------------------------
; main entry point
;--------------------------------------------------------------------------
MainInit:
#IF 0 ; Test some math macros ?
clrf freq2_hi
clrf freq2_mh
clrf freq2_ml
movlw .100
movwf freq2_lo
neg32 freq2 ; -100 = 0xFFFFFF9C
#ENDIF ; Test !
movlw PORT_A_IO ; initialise port A
errorlevel -302 ; Turn off banking message for the next few instructions..
bsf STATUS, RP0 ;! setting RP0 enables access to TRIS regs
movwf PORTA ;! looks like PORTA but is in fact TRISA
bcf STATUS, RP0 ;! clearing RP0 enables access to PORTs
clrf PORTA
movlw PORT_B_IO ; initialise port B
bsf STATUS, RP0 ;! setting RP0 enables access to TRIS regs
movwf PORTB ;! looks like PORTB but is in fact TRISB
bcf STATUS, RP0 ;! clearing RP0 enables access to PORTs
errorlevel +302 ; Enable banking message again
clrf PORTB
clrf disp_index ; initialise display index and
clrf disp_timer ; display multiplex timer
movlw BLANK ; blank character as dummy ...
movwf digit_8 ; for the lowest frequency display range
movlw TEST ; test all LED segments
call conv_char0
movlw TEST
call conv_char1
movlw TEST
call conv_char2
movlw TEST
call conv_char3
movlw TEST
call conv_char4
movlw PSC_DIV_BY_256 ; let the prescaler divide by 256 while testing..
call SetPrescaler ; safely write <W> into option register
#if(DEBUG==0)
; Do a LAMP TEST for half a second, including all decimal points :
movlw (LAMPTEST_LOOPS)>>8 ; high byte for 0.5 second lamp test
movwf gatecnt_hi
movlw (LAMPTEST_LOOPS)&0ffh ; low byte for 0.5 second lamp test
movwf gatecnt_lo
call count_pulses ; some delay to show the test pattern
#endif ; not DEBUG
MainRestart: ; Here we "restart" the counter after exiting from programming mode :
clrf psave_timer ; clear timer for power-save mode (no immediate power-down)
clrf psave_flags ; clear all power-saving flags (PSFLAG_ACTIVE, etc)
movlw foffs ; load destination address for reading from EEPROM...
movwf FSR ; ..into the PIC's pointer register
movlw EEPROM_ADR_FREQ_OFFSET+0 ; load the EEPROM-internal address offset (=source index)
call EEPROM_Read4Byte ; read from EEPROM: foffs..foffs+4 := EEPROM[W]
movlw options ; another destination address for reading from EEPROM..
movwf FSR ;
movlw EEPROM_ADR_OPTIONS ; load EEPROM-internal offset of "options"-byte
call EEPROM_ReadByte ; read single byte from EEPROM: options := EEEPROM[W]
#if(DEBUG==1)
bsf OPT_PWRSAVE ; enable power-save mode for debugger/simulator
#endif ; DEBUG
; Blank the display until 1st measurement is available :
call ClearDisplay
;--------------------------------------------------------------------------
; main loop : Preparation, auto ranging, measurement, conversion, display
;--------------------------------------------------------------------------
MainLoop:
; re-initialise ports
; ex: tris PORTA; tris PORTB
errorlevel -302 ; Turn off banking message for the next few instructions..
bsf STATUS, RP0 ;! setting RP0 enables access to TRIS regs
movlw PORT_A_IO ;!
movwf PORTA ;! looks like PORTA but is in fact TRISA
movlw PORT_B_IO ;!
movwf PORTB ;! looks like PORTB but is in fact TRISB
bcf STATUS, RP0 ;! clearing RP0 enables access to PORTs
clrwdt ; configure TMR0... but clear watchdog timer first
movlw b'100000' ; value for OPTION reg: edge - low-to-high transition,
; + prescaler assigned to Timer 0, 1:2
bsf STATUS, RP0 ;! setting RP0 enables access to OPTION reg
; option register is in bank1. i know. thanks for the warning.
movwf OPTION_REG ;! ex: "option" command (yucc)
bcf STATUS, RP0 ;! clearing RP0 for normal register access
errorlevel +302 ; Enable banking message again
#ifdef BAUDRATE_TEST
test1:
movlw 'A'
call tx_w
call bit_delay
call bit_delay
call bit_delay
call bit_delay
call bit_delay
call bit_delay
call bit_delay
call bit_delay
call bit_delay
call bit_delay
call bit_delay
call bit_delay
call bit_delay
call bit_delay
call bit_delay
call bit_delay
call bit_delay
call bit_delay
goto test1
#endif
; BAUDRATE_TEST
; First do a 'range-detection measurement' to find
; a suitable prescaler ratio. Worst-case-estimation:
; 50 MHz at the input of the async TIMER 0 prescaler
; requires a prescaler ratio of 64 because
; the synchron counter in TIMER 0 accepts a maximum
; frequency of f_osc / 4, here: max. 1 MHz.
; The theoretic maximum frequency is 64 MHz then, which
; was almost reached when tested with a PIC 16F628 .
; The range-detection interval is somewhere near 1/30 seconds (see RANGE_DET_LOOPS),
; so frequencies below 30*64 = 1920 Hz are not detectable at this step.
RANGE_DET_LOOPS equ CLOCK/(.30*CYCLES) ; number of gate-time loops to detect the MEASURING RANGE
; (which is required to find a good prescaler value)
movlw (RANGE_DET_LOOPS)>>8 ; high byte for RANGE DETECTION loop counter
movwf gatecnt_hi
movlw (RANGE_DET_LOOPS)&0ffh ; low byte for RANGE DETECTION loop counter
movwf gatecnt_lo
movlw PSC_DIV_BY_64 ; let the prescaler divide by 64 while testing..
call SetPrescaler ; safely write <W> into option register
call count_pulses ; count pulses for the range detection interval (1/16 sec)
; The result will be placed in freq_lo,freq_ml,freq_mh,freq_hi (32 bit)
; but the max count at 64 MHz input, 1/30 sec gate time, and prescaler=64 will be :
; 64MHz / (30 * 64) = 33333 pulses, so only 16 bits in the counter
; are required here (call them "testcount", f_in = testcount * 30*64) .
; The frequency resolution of this coarse measurement is 64*16 Hz = roughly 1 kHz.
; (for that reason it's not suited for "wake-up from power-save on frequency-change")
#if 0 ; TEST auto ranging
movlw (.8500)>>8 ; high byte of counted pulses
movwf freq_ml
movlw (.8500)&0ffh ; low byte of counted pulses
movwf freq_lo
#endif ; end TEST
; Load the default (soft-)counters for the GATE TIME.
; Most measuring ranges use a 1/4 second gate time !
movlw (GATE_TIME_LOOPS/4)>>8 ; high byte of gate time
movwf gatecnt_hi
movlw (GATE_TIME_LOOPS/4)&0ffh ; low byte of gate time
movwf gatecnt_lo
; Increment the "blinker" once every 0.25 seconds.
; (if the gate time is longer, flashing will be slower, that's acceptable)
incf blinker, f
incf psave_timer, f ; increment the power-save timer every 0.25 seconds too (checked somewhere else)
; Look at the range-detection count ("testcount")
; and decide which measuring range to use, beginning with the highest frequency range
#if (DISP_VARIANT==1)
; Ranges FOR VARIANT 1, 4 MHz CRYSTAL (low-power variant, less resolution at HF !)
; Rng testcount f_in prescaler gate_time display, resolution
; (1) 0..6 0.. 11.5 kHz 1 1 second X.XXXkHz, 0.001kHz (4 digits only)
; (2) 7..54 ..103.6 kHz 1 1/2 second XX.XXXkHz, 0.002kHz (last digit steps by 2)
; (3) 55..511 ..981.1 kHz 1 1/4 second XXX.XXkHz, 0.004kHz (last digit steps by 1)
; (4) 512..1023 .. 1.9 MHz 2 1/4 second XXX.XXkHz, 0.008kHz (last digit steps by 1)
; (5) 1024..2047 .. 3.9 MHz 4 1/4 second X.XXXXMHz, 0.016kHz (last digit steps by 1)
; (6) 2048..4095 .. 7.9 MHz 8 1/4 second X.XXXXMHz, 0.032kHz (last digit steps by 1)
; (7) 4096..8191 ... 15.7 MHz 16 1/4 second X.XXXXMHz, 0.064kHz (last digit steps by 1)
; (8) 8192..16383 ... 31.4 MHz 32 1/4 second X.XXXXMHz, 0.128kHz (last digit steps by 1 or 2)
; (9) 16384..33300 ... 63.9 MHz 64 1/4 second XX.XXXMHz, 0.256kHz (last digit steps by 1)
movfw freq_ml ; first look at bits 15..8 of the 'test count' result
andlw b'11000000' ; any of bits 15..14 set (>=16384) -> no Z flag -> range 9
btfss STATUS,Z ; skip next instruction if ZERO-flag set (!)
goto Range9 ; far jump to range 9
btfsc freq_ml,5 ; bit 13 set (>=8192) -> range 8
goto Range8
btfsc freq_ml,4 ; bit 12 set (>=4096) -> range 7
goto Range7
btfsc freq_ml,3 ; bit 11 set (>=2048) -> range 6
goto Range6
btfsc freq_ml,2 ; bit 10 set (>=1024) -> range 5
goto Range5
btfsc freq_ml,1 ; bit 9 set (>=512) -> range 4
goto Range4
btfsc freq_ml,0 ; bit 8 set (>=256) -> no Z flag -> range 3
goto Range3
movfw freq_lo ; now look at bits 7..0 only ..
sublw .54 ; subtract #54 - W register -> C=0 if result negative
btfss STATUS,C ; skip next instruction if C=1 (#54-W >= 0)
goto Range3 ; freq > 100kHz -> also range 3
movfw freq_lo ; look at bits 7..0 again ..
sublw .5 ; subtract #5 - W register -> C=0 if result negative
btfss STATUS,C ; skip next instruction if C=1
goto Range2 ; freq > 10kHz -> range 2
goto Range1 ; otherwise range 1
#endif ; end of specific range-switching for DISPLAY VARIANT #1
#if (DISP_VARIANT==2) || (DISP_VARIANT==3)
; Ranges FOR VARIANT 2+3, 20 MHz CRYSTAL (draws more power, but gives better resolution at HF )
; Even if PIC clocked with 20MHz, keep the input of TIMER0 below 4(!) MHz .
; Rng testcount f_in prescaler gate_time display, resolution
; (1) 0..6 0.. 11.5 kHz 1 1 second X.XXXkHz, 0.001kHz (4 digits only)
; (2) 7..54 ..103.6 kHz 1 1/2 second XX.XXXkHz, 0.002kHz (last digit steps by 2)
; (3) 44..2047 .. 3.9 MHz 1 1/4 second X.XXXXMHz, 4 Hz (last digit steps by 1)
; (4) 2048..4095 .. 7.9 MHz 2 1/4 second X.XXXXMHz, 8 Hz (last digit steps by 1)
; (5) 4096..8191 ... 15.7 MHz 4 1/4 second X.XXXXMHz, 16 Hz (last digit steps by 1)
; (6) 8192..16383 ... 31.4 MHz 8 1/4 second X.XXXXMHz, 32 Hz (last digit steps by 1 or 2)
; (7) 16384..33330 ... 63.9 MHz 16 1/4 second XX.XXXMHz, 64 Hz (last digit steps by 1)
movfw freq_ml ; first look at bits 15..8 of the 'test count' result
andlw b'11000000' ; any of bits 15..14 set (>=16384) -> no Z flag -> range 7
btfss STATUS,Z ; skip next instruction if ZERO-flag set (!)
goto Range7 ; far jump to range 7
btfsc freq_ml,5 ; bit 13 set (>=8192) -> range 6
goto Range6
btfsc freq_ml,4 ; bit 12 set (>=4096) -> range 5
goto Range5
btfsc freq_ml,3 ; bit 11 set (>=2048) -> range 4
goto Range4
btfsc freq_ml,2 ; bit 10 set (>=1024) -> range 3
goto Range3
btfsc freq_ml,1 ; bit 9 set (>=512) -> range 3
goto Range3
btfsc freq_ml,0 ; bit 8 set (>=256) -> no Z flag -> range 3
goto Range3
movfw freq_lo ; now look at bits 7..0 only ..
sublw .54 ; subtract #54 - W register -> C=0 if result negative
btfss STATUS,C ; skip next instruction if C=1 (#54-W >= 0)
goto Range3 ; freq > 100kHz -> also range 3
movfw freq_lo ; look at bits 7..0 again ..
sublw .5 ; subtract #5 - W register -> C=0 if result negative
btfss STATUS,C ; skip next instruction if C=1
goto Range2 ; freq > 10kHz -> range 2
goto Range1 ; otherwise range 1 (lowest frequencies)
#endif ; end of specific range-switching for DISPLAY VARIANT #2
Range1: ; Range 1: async prescaler off, 1 second gate time for very low frequencies :
call PrescalerOff ; turn hardware prescaler off
incf psave_timer, f ; increment power-save timer three more times
incf psave_timer, f ; (1 sec-gate instead of 0.25)
incf psave_timer, f
; Load the GATE TIMER (as count of loops) for this measuring range.
movlw (GATE_TIME_LOOPS)>>8 ; high byte for 1 second gate time
movwf gatecnt_hi
movlw (GATE_TIME_LOOPS)&0ffh ; low byte for 1 second gate time
movwf gatecnt_lo
; Load the count of "left shifts" to compensate gate time + prescaler :
movlw 0 ; no need to multiply with prescaler 1:1 and 1-sec gate time
goto GoMeasure
Range2: ; Range 2: async prescaler off, 1/2 second gate time for quite low frequencies :
call PrescalerOff ; turn hardware prescaler off
incf psave_timer, f ; increment power-save timer one more time (0.5 sec-gate instead of 0.25)
; Load the GATE TIMER (as count of loops) for this measuring range.
movlw (GATE_TIME_LOOPS/2)>>8 ; high byte for 1/2 second gate time
movwf gatecnt_hi
movlw (GATE_TIME_LOOPS/2)&0ffh ; low byte for 1/2 second gate time
movwf gatecnt_lo
; Load the count of "left shifts" to compensate gate time + prescaler :
movlw 1 ; multiply by 2 (=2^1) later to compensate gate time (1/2 s)
goto GoMeasure
Range3: ; Range 3: async prescaler off, gate time = default (1/4 sec) :
call PrescalerOff ; turn hardware prescaler off
movlw 2 ; multiply by 4 (=2^2) later to compensate gate time (1/4 s)
goto GoMeasure
Range4: ; Range 4: prescaler divide by 2 , gate time = default (1/4 sec) :
movlw PSC_DIV_BY_2 ; let the prescaler divide by 2 while MEASURING...
call SetPrescaler ; safely write <W> into option register
movlw 3 ; multiply by 8 (=2^3) later to compensate prescaling (1:2) * gate time (1/4 s)
goto GoMeasure
Range5: ; Range 5: prescaler divide by 4 , gate time = default (1/4 sec) :
movlw PSC_DIV_BY_4 ; let the prescaler divide by 2 while MEASURING...
call SetPrescaler ; safely write <W> into option register
movlw 4 ; multiply by 16 (=2^4) later to compensate prescaling (1:4) * gate time (1/4 s)
goto GoMeasure
Range6: ; Range 6: prescaler divide by 8 , gate time = default (1/4 sec) :
movlw PSC_DIV_BY_8 ; let the prescaler divide by 2 while MEASURING...
call SetPrescaler ; safely write <W> into option register
movlw 5 ; multiply by 32 (=2^5) later to compensate prescaling (1:8) * gate time (1/4 s)
goto GoMeasure
Range7: ; Range 7: prescaler divide by 16 , gate time = default (1/4 sec) :
movlw PSC_DIV_BY_16 ; let the prescaler divide by 2 while MEASURING...
call SetPrescaler ; safely write <W> into option register
movlw 6 ; multiply by 64 (=2^6) later to compensate prescaling (1:16) * gate time (1/4 s)
goto GoMeasure
#if (DISP_VARIANT==1) ; Ranges 8 + 9 are only needed for VARIANT 1 with 4-MHz crystal :
Range8: ; Range 8: prescaler divide by 32 , gate time = default (1/4 sec) :
movlw PSC_DIV_BY_32 ; let the prescaler divide by 2 while MEASURING...
call SetPrescaler ; safely write <W> into option register
movlw 7 ; multiply by 128 (=2^7) later to compensate prescaling (1:32) * gate time (1/4 s)
goto GoMeasure
Range9: ; Range 9: prescaler divide by 64 , gate time = default (1/4 sec) :
movlw PSC_DIV_BY_64 ; let the prescaler divide by 2 while MEASURING...
call SetPrescaler ; safely write <W> into option register
movlw 8 ; multiply by 256 (=2^8) later to compensate prescaling (1:64) * gate time (1/4 s)
goto GoMeasure
#endif ; (DISP_VARIANT==1)
GoMeasure: movwf adjust_shifts ; save the number of "arithmetic left shifts" for later
call count_pulses ; count pulses for 1, 1/2, or 1/8 s .
; Result in freq_lo,freq_ml,freq_mh,freq_hi (32 bit) now,
; NOT adjusted for the gate-time or prescaler division ratio yet.
;----------------- Power-saving mode ------------------------------------
; Power-saving mode enabled or about to be activated ?
btfss OPT_PWRSAVE ; Power-save mode enabled (from config) ?
goto PsNotBlanked
; Arrived here: power-saving is ENABLED through the config,
; but not necessarily ACTIVE at the moment .
; If power-save is already active, clear the display (may have 'flashed up')
btfsc PSFLAG_ACTIVE ; if power-save already 'ACTIVE'..
call ClearDisplay ; then clear the display (latch)
; Next: Check if the frequency has changed significantly
; since the last 'reload' of the power-save timer.
; To keep things simple, only look at the LOW BYTES of the
; 'current' and the 'old' frequency reading at this stage
; (BEFORE multiplying the result with two power adjust_shifts) .
; 'psave_freq_lo' is an "old" reading; 'freq_lo' the current frequency.
; Both are UNSIGNED 8-bit values !
movfw freq_lo ; get low-byte of current frequency
subwf psave_freq_lo, w ; W := freq_lo - psave_freq_lo
; Make the difference (new minus old frequency in W) positive :
movwf bTemp ; bTemp := (freq_lo - psave_freq_lo)
btfss bTemp,7 ; check the sign-bit (=MSB)
goto PsDiffPos ; difference already posivite, else :
comf bTemp,f ; bTemp := ~bTemp (for example, 0xFF -> 0x00)
incf bTemp,f ; add one for two's complement
PsDiffPos:; Arrived here: difference made positive, i.e. bTemp = abs(freq_lo - psave_freq_lo) .
; If the frequency-difference is 'quite high',
; turn off the flag PSFLAG_ACTIVE and clear the power-save-timer:
movfw bTemp ; W := abs(freq_lo - psave_freq_lo)
sublw PSAVE_MAX_DIFF ; W := PSAVE_MAX_DIFF - W ; C=0 if result negative (=large f-diff)
btfsc STATUS,C ; skip next instruction if large frequency difference
goto PsSmallDiff ;
PsLargeDiff: ; Arrived here: there's a LARGE difference between 'current' and 'old' frequency
bcf PSFLAG_ACTIVE ; Back to normal display mode
clrf psave_timer ; restart 'power-save' activation timer (with display ON)
movfw freq_lo ; set 'current' frequency as new 'old' frequency...
movwf psave_freq_lo ; for the next XX-second interval !
goto PsNotBlanked
PsSmallDiff: ; Arrived here: there's only a SMALL difference between 'current' and 'old' frequency .
btfsc PSFLAG_ACTIVE ; power-save already 'ACTIVE' ?
goto PsActive ; yes, already active -> check for flash-up
; Check the power-save timer; it may be time to turn the display OFF now :
movfw psave_timer ; if(psave_timer > PSAVE_DELAY_TIME ) ...
sublw PSAVE_DELAY_TIME ; subtract #PSAVE_DELAY_TIME - W -> C=0 if result negative
btfsc STATUS,C ; skip next instruction if carry=0 (PSAVE_DELAY_TIME-W < 0)
goto PsNoTimeout ; psave_timer still low, no 'timeout' yet !
; Arrived here: Display was on, but almost no change in frequency -> enter power-saving mode
movlw PSAVE_FLASHUP_TIME-1 ; let display flash up once before turning off
movwf psave_timer ; ... to avoid overflow when incrementing it later
bsf PSFLAG_ACTIVE ; set the flag 'power-save ACTIVE' to blank the display
movfw freq_lo ; save low-byte of frequency when ENTERING power-save mode
movwf psave_freq_lo
goto PsSleep ; sleep for the first 600-millisecond-interval now
PsActive: ; Here if power-saving mode already active .
; Check it it's time to let the display flash up for a short time
; to show the operator we're still alive !
movfw psave_timer ; if(psave_timer > PSAVE_DELAY_TIME ) ...
sublw PSAVE_FLASHUP_TIME ; subtract #PSAVE_FLASHUP_TIME - W -> C=0 if result negative
btfsc STATUS,C ; skip next instruction if (PSAVE_FLASHUP_TIME-psave_timer) < 0
goto PsSleep ; psave_timer still low, don't 'flash up' yet !
PsFlashUp: clrf psave_timer ; prepare timer for next period of darkness
movfw freq_lo ; avoid turning the display on ..
movwf psave_freq_lo ; .. if the VFO is only "slowly creeping"
clrf psave_timer ; restart 'power-save' activation timer (with display OFF)
goto PsNotBlanked ; and let the display flash up for one gate interval
PsNoTimeout: ; small frequency difference, AND psave_timer still low..
; Already in "power-save"-mode or normal display ?
btfss PSFLAG_ACTIVE ; check the flag 'power-save ACTIVE'
goto PsNotBlanked ; not set -> normal display (not blanked)
; Arrived here: 'Saving power', which means the display
; is blanked MOST of the time (but it may flash up every XX seconds
; to show the operator we're still alive) .
PsSleep: call Sleep150ms ; put CPU to sleep for ~500 milliseconds..
call Sleep150ms
call Sleep150ms
goto CheckProgMode ; skip integer->BCD conversion (save power)
PsNotBlanked: ; Display is not blanked for power-saving mode at the moment.
; If this 'absolute difference' is quite large,
; clear the power-save timer to prevent turning off the display
; within the next XX seconds :
; Reload the power-save-timer if there was a significant change
; since the last comparison.
PrepDisp: ; Prepare the frequency (32-bit 'unadjusted' integer) for display:
; Multiply freq by 2^adjust_shifts to adjust for the prescaling
; WQS 2 frequency in Hz here
; and the timing period . The result will be a frequency in HERTZ, 32-bit integer.
; Note: the adjustment factor may be ONE which means no shift at all.
tstf adjust_shifts
bz NoAdjust
Adjust: clrc
rlf freq_lo , f
rlf freq_ml , f
rlf freq_mh , f
rlf freq_hi , f
decfsz adjust_shifts, f
goto Adjust
NoAdjust:
; WAS 3
; Check the result against under- and overflow.
; (There should be none if the frequency didn't change too rapidly
; between the range-detection and the actual measurement )
movfw freq_hi ; underflow (freq = 0) ?
iorwf freq_mh,w
iorwf freq_ml,w
iorwf freq_lo,w
#ifndef RS232_OUT
; WAS this causes print to skip
bz freq_underflow ; branch if yes
#endif
; ! RS232_OUT
btfsc freq_hi,7 ; overflow (freq > 7FFfffffh) ?
goto freq_overflow ; branch if yes
; WAS 4
; freq in freq_hi, freq_mh, freq_ml, freq_lo ????
; 32 bit binary to BCD, display?
; Save the frequency value without offset for programming mode in 'freq2',
; because 'freq' will be lost when splitting it into digits.
movfw freq_hi
movwf freq2_hi
movfw freq_mh
movwf freq2_mh
movfw freq_ml
movwf freq2_ml
movfw freq_lo
movwf freq2_lo
; Add the programmable frequency offset
; (often used to add or subtract the intermediate frequency in superhet receivers)
add32 freq, foffs ; freq := freq+foffs; 32-bit
; If the result is negative, make it posisive
btfss freq_hi, 7 ; bit 7 of the most significant byte is the SIGN
goto f_positive ; skip the following MACRO if positive..
neg32 freq ; freq := -freq (32-bit)
f_positive:
call CvtAndDisplayFreq ; Convert <freq> into BCD and show it on the display
CheckProgMode:
; Arrived here, the frequency is still valid in 'freq2'
; but not in 'freq'. Poll the programming key,
; maybe the user wants to save this value as the new
; FREQUENCY OFFSET .
#ifdef RS232_OUT
; no prog loop in RS232_OUT, no offset, just a frequency counter.
goto MainLoop
#endif
; RS232_out
#if(DEBUG==0)
btfss IOP_PROG_MODE ; Button "program mode" pressed ?
goto EnterProgLoop ; Yes, enter programming mode !
#endif ; not DEBUG
goto MainLoop ; end of main loop
;--------------------------------------------------------------------------
; frequency underflow (frequency < 1Hz)
;--------------------------------------------------------------------------
freq_underflow:
movlw BLANK ; display underflow as " 0[0]"
call conv_char0
movlw BLANK
call conv_char1
movlw BLANK
call conv_char2
movlw 0 ; why not 'zero' in the last digit ?
call conv_char3
movlw BLANK
call conv_char4 ; because the 5th digit is OPTIONAL !
goto CheckProgMode
;--------------------------------------------------------------------------
; frequency overflow (frequency > 50MHz)
;--------------------------------------------------------------------------
freq_overflow:
movlw BLANK ; display overflow as " E"
call conv_char0
movlw BLANK
call conv_char1
movlw BLANK
call conv_char2
movlw CHAR_E
call conv_char3
movlw BLANK
call conv_char4 ; Note that the 5th digit is OPTIONAL !
goto MainLoop ; end of main loop
;--------------------------------------------------------------------------
; program loop :
; - show a simple menu to select ADD or SUBTRACT offset,
; - save the frequency offset value permanently in DATA EEPROM,
; - return to the main loop when done .
;--------------------------------------------------------------------------
ProgModeDisplay ; Subroutine to update the LED display in programming mode + delay
movlw (PROGMODE_LOOPS)>>8 ; high byte for delay loops (usually 0.1 second)
movwf gatecnt_hi
movlw (PROGMODE_LOOPS)&0ffh ; low byte for delay loops
movwf gatecnt_lo
goto count_pulses ; update mux display + some delay + return
PmDisp_Quit: ; show "quit" on first 4 digits (quit programming mode)
movlw CHAR_Q
call conv_char0
movlw CHAR_u
call conv_char1
movlw CHAR_i
call conv_char2
movlw CHAR_t
PmDisp4: call conv_char3 ; for menu items with 4 characters
movlw BLANK
PmDisp5: call conv_char4
goto ProgModeDisplay
PmDisp_PSave: ; show "PSave" or "Pnorm", depending on power-save flag
btfss OPT_PWRSAVE ; Power-save mode active ?
goto PMD_NoPwSave
movlw CHAR_P ; if so, print "PSAVE"..
call conv_char0
movlw CHAR_S
call conv_char1
movlw CHAR_A
call conv_char2
movlw CHAR_V
call conv_char3
movlw CHAR_E
goto PmDisp5
PMD_NoPwSave: ; else print "NoPSV"
movlw CHAR_N
call conv_char0
movlw CHAR_o
call conv_char1
movlw CHAR_P
call conv_char2
movlw CHAR_S
call conv_char3
movlw CHAR_V
goto PmDisp5
PmDisp_Add: ; show "Add " on first 4 digits (add frequency offset)
movlw CHAR_A
call conv_char0
movlw CHAR_d
call conv_char1
movlw CHAR_d
call conv_char2
movlw BLANK
goto PmDisp4
PmDisp_Sub: ; show "Sub " on first 4 digits (subtract frequency offset)
movlw CHAR_S
call conv_char0
movlw CHAR_u
call conv_char1
movlw CHAR_b
call conv_char2
movlw BLANK
goto PmDisp4
PmDisp_Zero: ; show "Zero" on first 4 digits (set frequency offset to zero)
movlw CHAR_Z
call conv_char0
movlw CHAR_E
call conv_char1
movlw CHAR_r
call conv_char2
movlw CHAR_o
goto PmDisp4
PmDisp_StIF: ; show "taBLE" on first 4 digits (select standard IF)
movlw CHAR_t
call conv_char0
movlw CHAR_A
call conv_char1
movlw CHAR_b
call conv_char2
movlw CHAR_L
call conv_char3
movlw CHAR_E
call conv_char4
goto ProgModeDisplay
PmDisp_IF_1: ; show 1st standard IF from table
movlw EEPROM_ADR_STD_IF_TABLE + 4*0
goto PmLoadFreq2
PmDisp_IF_2: ; show 2nd standard IF from table
movlw EEPROM_ADR_STD_IF_TABLE + 4*1
goto PmLoadFreq2
PmDisp_IF_3: ; show 3rd standard IF from table
movlw EEPROM_ADR_STD_IF_TABLE + 4*2
goto PmLoadFreq2
PmDisp_IF_4: ; show 4th standard IF from table
movlw EEPROM_ADR_STD_IF_TABLE + 4*3
goto PmLoadFreq2
PmDisp_IF_5: ; show 5th standard IF from table
movlw EEPROM_ADR_STD_IF_TABLE + 4*4
goto PmLoadFreq2
PmLoadFreq2: ; Load <freq2> from EEPROM[w] and show it on the display
movwf bTemp
movlw freq2 ; load the ADDRESS of 'freq2' ...
movwf FSR ; ... into the PIC's "pointer" register
movfw bTemp ; and the EEPROM-internal offset into W
call EEPROM_Read4Byte ; read <freq2> from EEPROM : *FSR = EEPROM[W]
movlw freq2 ; load the ADDRESS of 'freq2' again ...
movwf FSR ; ... into the PIC's "pointer" register
call ShowInt32_FSR ; Splitt <*FSR> (32 bit integer) to 8 BCD-digits...
goto ProgModeDisplay ; and show it for 0.1 seconds, maybe more
; "Execution" of the selectable menu items. Invoked after long key press.
PmExec_Quit: ; quit programming mode (without changing anything)
goto MainRestart
PmExec_PSave: ; turn power-saving mode on/off
movlw 0x01 ; bit0 = power-save
xorwf options,f ; toggle Power-save flag in sofware-"options" register
movlw options ; load the ADDRESS of 'options' ...
movwf FSR ; ... into the PIC's "pointer" register
movlw EEPROM_ADR_OPTIONS ; load the EEPROM-internal address offset (=destination)
call SaveInEEPROM ; write *FSR into EEPROM[w] (bits 31..24)
goto ProgModeDisplay
PmExec_Add: ; add frequency offset from now on .
; This is achieved by saving the currently measured frequency
; in EEPROM memory and restarting the counter.
SaveFreq2: ; save <freq2> (4 bytes) in the PIC's EEPROM memory :
movlw freq2 ; load the ADDRESS of 'freq2' ...
movwf FSR ; ... into the PIC's "pointer" register
movlw EEPROM_ADR_FREQ_OFFSET ; load the EEPROM-internal address offset (=destination)
call SaveInEEPROM ; write *FSR into EEPROM[w] (bits 31..24)
incf FSR, f ; next source address please
movlw EEPROM_ADR_FREQ_OFFSET+1 ; next destination address
call SaveInEEPROM ; write *FSR into EEPROM[w] (bits 23..16)
incf FSR, f ; next source address please
movlw EEPROM_ADR_FREQ_OFFSET+2 ; next destination address
call SaveInEEPROM ; write *FSR into EEPROM[w] (bits 15..8)
incf FSR, f ; next source address please
movlw EEPROM_ADR_FREQ_OFFSET+3 ; next destination address
call SaveInEEPROM ; write *FSR into EEPROM[w] (bits 7..0)
goto MainRestart ; restart with new frequency offset
PmExec_Sub: ; subtract frequency offset from now on
; This is achieved by making 'freq2' negative (two's complement)
; and then saving it in EEPROM.
neg32 freq2 ; freq2 := -freq2 (32 bit)
goto SaveFreq2 ; save freq2 in EEPROM and restart
PmExec_Zero: ; set frequency offset to zero
clrf freq2_hi ; freq2 := 0 (32 bit)
clrf freq2_mh ; ... medium high byte
clrf freq2_ml ; ... medium low byte
clrf freq2_lo ; ... low byte
goto SaveFreq2 ; save freq2 in EEPROM and restart
PmExec_StIF ; switch to "Standard IF selection mode"
movlw MI_IF_1
PmExec_SetMI: movwf menu_index
goto ProgLoop ;
PmExec_SelIF ; Finished selecting a "standard IF" from table.
; Switch back to the main menu, and let
; the user decide if the offset is positive (add)
; or negative (sub).
movlw MI_ADD ; Suggestion: ADD the offset
goto PmExec_SetMI
EnterProgLoop:
; Prepare 'program mode' :
clrf menu_index
; Show "Prog" on the display
movlw CHAR_P
call conv_char0
movlw CHAR_r
call conv_char1 ; show "Prog" on the display..
movlw CHAR_o
call conv_char2
movlw CHAR_G
call conv_char3
movlw BLANK ; Note that the 5th digit is OPTIONAL so we don't use it here
call conv_char4
; wait until the operator releases the "Prog" key, while display runs
Enter2: call ProgModeDisplay ; update mux display + provide some delay
btfss IOP_PROG_MODE ; Button "program mode" still pressed ?
goto Enter2 ; yes, continue loop while displaying "Prog"
ProgLoop:
incf blinker, f ; Toggle the blink flag (for flashing for kHz-point)
; Show "quit", "add", "sub", "zero", ... on the display depending on menu_index (0..3)
call PMDisplay ; show string[menu_index] on LED display (from table)
btfsc IOP_PROG_MODE ; "program key" pressed now ? (low means pressed)
goto ProgLoop ; no, wait until user presses it
; Arrived here, the key is PRESSED. The question is how long...
; A short press means "advance to the next menu index" ,
; a longer press means "execute the selected function" .
; Everything under 1 second is considered a "short press".
movlw .10 ; 10 * 0.1 sec
movwf menu_timer
ChkKey: btfsc IOP_PROG_MODE ; "program key" still pressed ? (low means pressed)
goto ShortPress ; no, key released, it was a SHORT press (less than 0.5 seconds)
call ProgModeDisplay ; wait another 100 milliseconds
decfsz menu_timer, f ; decrement timer and skip next instruction if NOT zero
goto ChkKey ;
; Arrived here, it's a LONG key press, but the key is still down..
; Wait until the operator releases the "Prog" key
; Show a BLINKING display while the button is pressed,
; as an indicator for the user to release the button now.
Release2: call ClearDisplay ; fill display latch with blanking pattern
call ProgModeDisplay ; show blank display for 0.1 seconds
call PMDisplay ; show string[menu_index] for 0.1 seconds
btfss IOP_PROG_MODE ; Button "program mode" still pressed ?
goto Release2 ; yes, wait for button release, otherwise..
goto PMExecute ; Execute the function belonging to menu_index
ShortPress: ; advance to the next menu index, but don't execute the associated function
movfw menu_index
sublw MI_INDEX_MAX ; subtract #MI_INDEX_MAX - W register -> C=0 if result negative ("W too large")
btfsc STATUS,Z ; skip next instruction if Z=0
goto LastMainMenu ; Z=1 means "this is the last item in the main menu"
btfss STATUS,C ; skip next instruction if C=1
goto NotMainMenu ; C=0 means "this is not the main menu"
incf menu_index, f ; menu_index := menu_index+1
goto ProgLoop ; end of programming loop
LastMainMenu:
clrf menu_index ; wrap to 1st menu index
goto ProgLoop
NotMainMenu: ; not main menu, but sub-menu ..
movfw menu_index
sublw MI_IF_SUBMENU_MAX ; subtract #MI_.. - W register -> C=0 if result negative ("W too large")
btfsc STATUS,Z ; skip next instruction if Z=0
goto LastIfSubMenu ; Z=1 means "this is the last item in the main menu"
btfss STATUS,C ; skip next instruction if C=1
goto NotIfSubMenu ; C=0 means "this is not the main menu"
incf menu_index, f ; menu_index := menu_index+1 (in submenu)
goto ProgLoop ;
LastIfSubMenu: ; was in the last "standard IF submenu"..
movlw MI_IF_1 ; back to the 1st standard IF submenu
movwf menu_index
goto ProgLoop
NotIfSubMenu: ; was not in the "standard IF submenu"..
clrf menu_index ; must be an error; back to main menu
goto ProgLoop
END ; directive 'end of program'