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'
file: /Techref/microchip/freq2rs232-jp.htm, 114KB, , updated: 2010/4/12 13:30, local time: 2024/8/14 20:06,
18.224.34.53:LOG IN
|
©2024 These pages are served without commercial sponsorship. (No popup ads, etc...).Bandwidth abuse increases hosting cost forcing sponsorship or shutdown. This server aggressively defends against automated copying for any reason including offline viewing, duplication, etc... Please respect this requirement and DO NOT RIP THIS SITE. Questions? <A HREF="http://ecomorder.com/techref/microchip/freq2rs232-jp.htm"> PIC 16F628 Frequency Counter with RS232 output in a DB9 backshell by Jan Panteltje</A> |
Did you find what you needed? |
Welcome to ecomorder.com! |
Ashley Roll has put together a really nice little unit here. Leave off the MAX232 and keep these handy for the few times you need true RS232! |
.