;************************************************************************** ; FILE: C:\PIC\freq_counter\counter1.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) * ; 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=16F628 #include ; processor specific definitions #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 ;************************************************************************** ; * ; 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) ; 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 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 movwf LEDS_PORT ; turn LED segments off ; 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' . ;-------------------------------------------------------------------------- conv macro display ; macro for duplicate code movwf display ; save decimal point bit (msb) andlw 7fh ; mask bit call Digit2SevenSeg ; convert digit into 7-segment-code via table btfsc display,7 ; check bit 7 = decimal point ? #if(COMMON_CATHODE) iorlw 1<] 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