The Weeder Frequency Counter

Expanded by Barry Smith

16F628, 4 Bit LCD, USART output

Here is my updated version of the driver code for this using the PIC16F628 as a drop in replacement for the F84 (F84 is also still supported). The other main changes are a switch to 4-bit LCD interface to free up some pins and USART output so the frequency can be fed to a PC COM port.

;******************************************************************************
;	FREQUENCY COUNTER
;	Model  : WTCNT
;	Author : Barry Smith (tron at zoidberg dot nl)
;	Date   : April 2006
;	Version: 0.1
;
;	Adapted from original code by Terry J. Weeder (WWW.WEEDTECH.COM)
;
;	The main differences between this code and the original code are:
;
;	- Code ported to the 16F628 (should work on all 16F6xx chips)
;
;   - Data also sent to USART port to allow PC to read frequency.
;
;	- LCD interface modified to 4 data lines to free up I/O pins
;
;	- Code uses MPASM include file for all register definitions
;
;	- Various code tweaks and changes
;
;	Distributed under the GNU public license. This code may be used in
;   commercial products provided this source code is also made available.
;   If this code is modified the modified source must be made available.
;
;******************************************************************************

	IFDEF __16F84A
		include p16f84a.inc
		__CONFIG(_CP_OFF & _WDT_OFF & _PWRTE_ON & _XT_OSC)
	ENDIF

	IFDEF __16F628
		include p16f628.inc
		__CONFIG(_BODEN_OFF & _CP_OFF & _DATA_CP_OFF & _PWRTE_ON & _WDT_OFF & _LVP_OFF & _MCLRE_OFF & _XT_OSC)
		#define SERIAL_OUT	; Send data via USART
	ENDIF

; #define NO_LCD	; Define to work without LCD present (eg RS232 only)
; #define LCD_8X2		; LCD is an 8x2 screen

LCD_E	equ	0	; Port A, bit 0
LCD_RW	equ	1	; Port A, bit 1
LCD_RS	equ	2	; Port A, bit 2
OVRFLW	equ	7

	IFDEF LCD_8X2
LCD_START	equ		0x80	; Start address on display
	ELSE
LCD_START	equ		0x82
	ENDIF

	cblock	0x20
	gate
	cnt1, cnt2, cnt3
	calc1, calc2, calc3
	sum1, sum2, sum3
	rtcc2
	DIGITS:0x07
	count1
	count2
	in_reg
	addcnt
	status2
	TBUF:0x0E	; Text Buffer
	TBUF_PTR	; Current position in tbuffer
	TSAVE		; Save INDF when using tbuffer
	TWTMP		; Also used in text buffering
	LW_HIGH
	LW_LOW
	endc
;
	org	0
	goto	start
;		
; Writes a character to the end of the text buffer
; Preserves: W, FSR
; Destroys : STATUS
tbuffer
	movwf	TWTMP			; Save W
	movf	FSR, W			; Save FSR
	movwf	TSAVE

	movf	TBUF_PTR, W		; Assign pointer to FSR
	movwf	FSR
	movf	TWTMP, W		; Write W value to pointer location
	movwf	INDF
	incf	TBUF_PTR, F		; Increment pointer..
	incf	FSR, F			; .. and null terminate string.
	clrf	INDF

	movf	TSAVE, W		; Restore FSR
	movwf	FSR
	movf	TWTMP, W		; restore W, in case its useful
	return
;
; Writes the contents of the tbuffer out
tbuffer_out

	IFNDEF NO_LCD	; LCD only section

	movlw	LCD_START
	call	lcd_inst_out
	movlw	TBUF
	movwf	FSR
tbo_loop
	movf	INDF, W
	btfsc	STATUS, Z
	goto	tbo_out2
	call	lcd_data_out
	incf	FSR, F
	goto	tbo_loop

	ENDIF			; End LCD only section
tbo_out2
	IFDEF SERIAL_OUT
	movlw	TBUF
	movwf	FSR
tbo_loop2
	movf	INDF, W
	btfsc	STATUS, Z
	goto	tbo_end
	call	tx_data_out
	incf	FSR, F
	goto	tbo_loop2
tbo_end
	movlw	'\n'		; Newline terminate
	call	tx_data_out
	ENDIF

	return

	IFDEF SERIAL_OUT
tx_data_out
	btfss	PIR1, TXIF
	goto	tx_data_out	; Wait for buffer to empty
	movwf	TXREG
	return
	ENDIF
;
int_del
	movlw	0x05		;delay 2.5 ms (4 MHz clock)
	movwf	count1
d1
	movlw	0xA5
	movwf	count2
d2
	decfsz	count2	,f
	goto	d2
	decfsz	count1	,f
	goto	d1
	retlw	0x00
;
sub
	bcf	status2,OVRFLW	;clear overflow bit
	movf	calc1,w		;subtract calc1 from cnt1 
	subwf	cnt1	,f
	btfsc	STATUS,C
	goto	sb1
	movlw	0x01		;borrow from cnt2 if overflow
	subwf	cnt2	,f
	btfsc	STATUS,C
	goto	sb1
	subwf	cnt3	,f		;borrow from cnt3 if cnt2 overflow
	btfss	STATUS,C
	bsf	status2,OVRFLW	;set overflow bit if result is negative
sb1
	movf	calc2,w		;subtract calc2 from cnt2
	subwf	cnt2	,f
	btfsc	STATUS,C
	goto	sb2
	movlw	0x01		;borrow from cnt3 if cnt2 overflow
	subwf	cnt3	,f
	btfss	STATUS,C
	bsf	status2,OVRFLW	;set overflow bit if result is negative
sb2
	movf	calc3,w		;subtract calc3 from cnt3
	subwf	cnt3	,f
	btfss	STATUS,C
	bsf	status2,OVRFLW	;set overflow bit if result is negative
	retlw	0x00
;
add
	movf	calc1,w		;add calc1 to cnt1
	addwf	cnt1	,f
	btfss	STATUS,C
	goto	ad1
	incfsz	cnt2	,f		;add to cnt2 if cnt1 overflow
	goto	ad1
	incf	cnt3	,f		;add to cnt3 if cnt2 overflow
ad1
	movf	calc2,w		;add calc2 to cnt2
	addwf	cnt2	,f
	btfsc	STATUS,C
	incf	cnt3	,f		;add to cnt3 if cnt2 overflow
	movf	calc3,w		;add calc3 to cnt3
	addwf	cnt3	,f
	retlw	0x00
;
; Begin by writing '/' (or 0x2F) to DIGITS+0 - 7
cnvt
	movlw	0x07		;7 digits in display
	movwf	count1
	movlw	DIGITS+0		;set FSR for MSB in display
	movwf	FSR
	movlw	0x2F		;one less that ASCII "0"
cnvt0
	movwf	INDF
	incf	FSR	,f
	decfsz	count1,F
	goto	cnvt0

	movlw	0x0F		;load "1,000,000" in calc1-3 (0x0F4240)
	movwf	calc3
	movlw	0x42
	movwf	calc2
	movlw	0x40
	movwf	calc1
cnvt1
	call	sub				;subtract number from count
	incf	DIGITS+0	,f		;increment 1,000,000's register
	movlw	0x3A
	xorwf	DIGITS+0,w
	btfsc	STATUS,Z
	goto	overflow
	btfss	status2,OVRFLW	;check if overflow
	goto	cnvt1
	call	add		;add back last number
	movlw	0x01		;load "100,000" in calc1-3
	movwf	calc3
	movlw	0x86
	movwf	calc2
	movlw	0xA0
	movwf	calc1
cnvt2
	call	sub		;subtract number from count
	incf	DIGITS+1	,f		;increment 100,000's register
	btfss	status2,OVRFLW	;check if overflow
	goto	cnvt2
	call	add		;add back last number
	clrf	calc3		;load "10,000" in calc1-3
	movlw	0x27
	movwf	calc2
	movlw	0x10
	movwf	calc1
cnvt3
	call	sub		;subtract number from count
	incf	DIGITS+2	,f		;increment 10,000's register
	btfss	status2,OVRFLW	;check if overflow
	goto	cnvt3
	call	add		;add back last number
	movlw	0x03		;load "1,000" in calc1-3
	movwf	calc2
	movlw	0xE8
	movwf	calc1
cnvt4
	call	sub		;subtract number from count
	incf	DIGITS+3	,f		;increment 1,000's register
	btfss	status2,OVRFLW	;check if overflow
	goto	cnvt4
	call	add		;add back last number
	clrf	calc2		;load "100" in calc1-3
	movlw	0x64
	movwf	calc1
cnvt5
	call	sub		;subtract number from count
	incf	DIGITS+4	,f		;increment 100's register
	btfss	status2,OVRFLW	;check if overflow
	goto	cnvt5
	call	add		;add back number
	movlw	0x0A		;load "10" in calc1-3
	movwf	calc1
cnvt6
	call	sub		;subtract number from count
	incf	DIGITS+5	,f		;increment 10's register
	btfss	status2,OVRFLW	;check if overflow
	goto	cnvt6
	call	add		;add back last number
	movf	cnt1,w		;put remainder in 1's register
	addwf	DIGITS+6	,f
	incf	DIGITS+6	,f
	retlw	0x00
;
count
	BANKSEL	OPTION_REG
	movlw	b'00110111'	;rtcc = ext, 1/256
	movwf	OPTION_REG
	BANKSEL	TRISA
	movlw	b'00010000'	;define PORTA as output 
	movwf	TRISA
	BANKSEL	PORTA
	bcf	PORTA,3
	bcf	PORTA,2
	clrf	cnt3
	clrf	TMR0
	clrf	rtcc2
	bsf	PORTA,2	;toggle rtcc pin
	bcf	PORTA,2
	movf	gate,w		;get gate time
	movwf	count1
	bsf	PORTA,3	;start count
fr4	movlw	0xFA
	movwf	count2
	goto	fr6
fr5	nop
	nop
	nop
	nop
	nop
	nop
fr6	movf	TMR0,w		;test for rtcc rollover (12)
	subwf	rtcc2	,f
	btfss	STATUS,Z
	goto	fr7
	nop
	goto	fr8
fr7	btfsc	STATUS,C
	incf	cnt3	,f
fr8	movwf	rtcc2
	nop
	nop
	nop
	decfsz	count2	,f
	goto	fr5
	decfsz	count1	,f
	goto	fr4
	bcf	PORTA,3	;stop count
	movf	TMR0,w		;get rtcc count
	movwf	cnt2
	subwf	rtcc2	,f		;test for rtcc rollover
	btfss	STATUS,C
	goto	fr9
	btfss	STATUS,Z
	incf	cnt3	,f
fr9	clrf	cnt1		;set to get prescaler count
fr10	decf	cnt1	,f
	bsf	PORTA,2	;toggle rtcc pin
	bcf	PORTA,2
	movf	TMR0,w		;test if rtcc has changed
	xorwf	cnt2,w
	btfsc	STATUS,Z
	goto	fr10
	retlw	0x00
;
;
	IFNDEF NO_LCD

lcd_data_out
	bsf		PORTA, LCD_RS
	goto	lcd_w_out
lcd_inst_out
	bcf		PORTA, LCD_RS
;
; 4 bit W out routine
;
lcd_w_out
	movwf	LW_HIGH
	movwf	LW_LOW

	bcf		PORTA, LCD_RW
	movlw	b'00001111'	; Set RB7:4 as outputs
	BANKSEL	TRISB
	andwf	TRISB, F
	BANKSEL	PORTB

	movlw	b'11110000'
	andwf	LW_HIGH, F
	movlw	b'00001111'
	andwf	LW_LOW, F
	swapf	LW_LOW, F	; Now LW_HIGH = 7654xxxx, LW_LOW = 3210xxxx from orig. W

	movf	PORTB, W
	andlw	b'00001111'
	iorwf	LW_HIGH, W
	movwf	PORTB		; Writes high order bits out
	bsf		PORTA, LCD_E
	nop
	bcf		PORTA, LCD_E

	movf	PORTB, W
	andlw	b'00001111'
	iorwf	LW_LOW, W
	movwf	PORTB			; Writes low order bits out
	bsf		PORTA, LCD_E
	nop
	bcf		PORTA, LCD_E
lcd_busy_wait
	call	lcd_w_in
	movwf	LW_HIGH
	btfss	LW_HIGH, 7
	goto	lcd_shift
	goto	lcd_busy_wait
;
lcd_shift
	IFDEF LCD_8X2
	movlw	0x08
	xorwf	LW_HIGH, W
	btfss	STATUS, Z
	return
	movlw	0xC4
	call	lcd_inst_out
	ENDIF
	return
;
; Routine to read the BF/Addr information from the LCD
;
lcd_w_in
	movlw	b'11110000'		; Set RB7:4 as inputs
	BANKSEL	TRISB
	iorwf	TRISB, F
	BANKSEL PORTB
	bcf		PORTA, LCD_RS
	nop						; Safety nop
	bsf		PORTA, LCD_RW
	nop						; Safety nop
	bsf		PORTA, LCD_E	; Trigger read (high bits)
	nop
	movf	PORTB, W
	bcf		PORTA, LCD_E	; Clock low
	movwf	LW_HIGH
	bsf		PORTA, LCD_E	; Trigger read (low bits)
	movlw	b'11110000'
	andwf	LW_HIGH, F
	swapf	PORTB, W
	bcf		PORTA, LCD_E
	andlw	b'00001111'
	iorwf	LW_HIGH, F
	
	bcf		PORTA, LCD_RW
	movlw	b'00001111'		; Reset RB7:4 as outputs
	BANKSEL	TRISB
	andwf	TRISB, F
	BANKSEL	PORTB
	
	movf	LW_HIGH, W
	return

	ENDIF	; end LCD only section
;
;******************************************************************************
;                                   START
;******************************************************************************
;
start
	clrf	PORTA		;instruction, write, enable low

	IFDEF __16F628
		movlw	0x07
		movwf	CMCON
	ENDIF

	movlw	b'00010000'
	BANKSEL	TRISA
	movwf	TRISA

	BANKSEL	PORTB
	clrf	PORTB
	movlw	b'00000110'	; For 628 RX/TX must be inputs
	BANKSEL	TRISB
	movwf	TRISB
	BANKSEL	PORTB

	IFDEF SERIAL_OUT
	BANKSEL	SPBRG
	movlw	d'25'		; 9600 baud (with BRGH=1)
	movwf	SPBRG
	BANKSEL	TXSTA
	bcf		TXSTA, SYNC
	bsf		TXSTA, TXEN
	bsf		TXSTA, BRGH
	BANKSEL	RCSTA
	bsf		RCSTA, SPEN
	ENDIF

	IFNDEF NO_LCD

; It is necessary to wait for 15ms after Vcc rises to
; 4.5V and 40ms after it rises to 2.7V. However, as we
; use PWRTE_ON which gives a 72ms delay, this should be
; fine and we should need no extra delay here.
;
	movlw	b'00001111'		; Note: PORTA is low so RS & RW
	andwf	PORTB,F			;       are both 0.
	movlw	b'00110000'		; Puts 0011 on lcd D7:D4
	iorwf	PORTB, F		; yes, this does mean 8 bit!
	bsf		PORTA,LCD_E		; toggle enable
	nop
	bcf		PORTA,LCD_E
	call	int_del
	call	int_del			; Wait at least 4.1ms (5ms here)
	bsf		PORTA,LCD_E		; Send 8-bit initialise again...
	nop
	bcf		PORTA,LCD_E

	movlw	d'50'			; Now need 100us delay
	movwf	TWTMP
lcd_init_del1
	decfsz	TWTMP, F
	goto	lcd_init_del1	; about 150us in fact...

	bsf		PORTA, LCD_E	; Yet again we set it to 8-bit
	nop						; what a wierd initialisation
	bsf		PORTA, LCD_E	; this is!

	bcf		PORTB, 4		; Change 8 line -> 4 line
	nop
	bsf		PORTA,LCD_E		; Finally we can send the 4-bit init
	nop
	bcf		PORTA,LCD_E
	movlw	d'15'			; Now need 40us delay
	movwf	TWTMP
lcd_init_del2
	decfsz	TWTMP, F
	goto	lcd_init_del2	; about 150us in fact...

; Finally we can send 4-bit instructions, starting with
; the yet another initialisation instruction.

	movlw	b'00101000'		;initialise (again)
	call	lcd_inst_out
	movlw	b'00001100'		;display on, cursor off
	call	lcd_inst_out
	movlw	b'00000001'		;clear display
	call	lcd_inst_out
	movlw	b'00000110'		;entry mode
	call	lcd_inst_out
	movlw	b'10000000'		;cursor to start
	call	lcd_inst_out

	ENDIF	;	end LCD only section
;
mhz	
	movlw	0x14		;0.1 sec gate
	movwf	gate
	call	count
	call	cnvt		;convert binary to ASCII
;
; If first 2 digits are zeros, goto khz1, otherwise mhz1
;
	movlw	'0'			;test if "0"
	xorwf	DIGITS+0,w
	btfss	STATUS,Z
	goto	mhz1
	movlw	'0'			;if 1st digit is 0... test if "0"
	xorwf	DIGITS+1,w
	btfsc	STATUS,Z
	goto	khz1
mhz1
	movlw	TBUF		; Reset TBUF pointer
	movwf	TBUF_PTR
	movlw	0x02		;output first 2 characters
	movwf	count1
	movlw	DIGITS+0		;MSD of freq
	movwf	FSR
;
; mhz2/mhz3 loop puts out 2 (count1) digits, printing spaces for leading zeros.
;
mhz2
	movlw	'0'		;test if "0"
	xorwf	INDF,w
	btfss	STATUS,Z
	goto	mhz3
	movlw	' '		;change preceeding "0's" to "space"
	call	tbuffer
	incf	FSR	,f
	decfsz	count1	,f
	goto	mhz2
	goto	mhz4
mhz3
	movf	INDF,w
	call	tbuffer
	incf	FSR	,f
	decfsz	count1,f
	goto	mhz3
;
; We know we're dealing with Mhz here, or we would have jumped to kHz
; by now, so put out '.'
;
mhz4
	movlw	'.'
	call	tbuffer
	movlw	0x05		;output last 5 characters
	movwf	count1	
mhz5
	movf	INDF,w
	call	tbuffer
	incf	FSR	,f
	decfsz	count1	,f
	goto	mhz5
	movlw	' '
	call	tbuffer
	movlw	'M'
	call	tbuffer
	movlw	'H'
	call	tbuffer
	movlw	'z'
	call	tbuffer
	movlw	' '
	call	tbuffer
	movlw	' '
	call	tbuffer
	call	tbuffer_out
	goto	mhz
;
; If first 2 digits were 00 then we try here for kHz
;
khz
	movlw	0x14		;0.1 sec gate
	movwf	gate
	call	count
	call	cnvt		;convert binary to BCD
	movlw	'0'		;test if 0
	xorwf	DIGITS+0,w
	btfss	STATUS,Z
	goto	mhz1
	movlw	'2'		;test if < 2
	subwf	DIGITS+1,w
	btfsc	STATUS,C
	goto	mhz1
	movlw	'0'		;test if "0"
	xorwf	DIGITS+1,w
	btfss	STATUS,Z
	goto	khz1
	movlw	'0'		;test if "0"
	xorwf	DIGITS+2,w
	btfsc	STATUS,Z
	goto	xkhz
khz1
	movlw	TBUF		; Reset TBUF pointer
	movwf	TBUF_PTR
	movlw	0x05		; output first 5 characters
	movwf	count1
	movlw	DIGITS+0	; MSD of freq
	movwf	FSR
khz2
	movlw	'0'			; test if "0"
	xorwf	INDF,w
	btfss	STATUS,Z
	goto	khz3
	movlw	' '			; change preceeding "0's" to "space"
	call	tbuffer
	incf	FSR	,f
	decfsz	count1	,f
	goto	khz2
	goto	khz4
khz3
	movf	INDF,w
	call	tbuffer
	incf	FSR	,f
	decfsz	count1	,f
	goto	khz3
khz4
	movlw	'.'
	call	tbuffer
	movf	INDF,w		;output last 2 characters
	call	tbuffer
	incf	FSR	,f
	movf	INDF,w
	call	tbuffer
	movlw	' '
	call	tbuffer
	movlw	'k'
	call	tbuffer
	movlw	'H'
	call	tbuffer
	movlw	'z'
	call	tbuffer
	movlw	' '
	call	tbuffer
	movlw	' '
	call	tbuffer
	call	tbuffer_out
	goto	khz
;
; Now routines with modified 1 sec gates for < 100kHz
;
xkhz
	movlw	0xC8		;1 sec gate
	movwf	gate
	call	count
	call	cnvt		;convert binary to BCD
	movlw	'0'		;test if 0
	xorwf	DIGITS+0,w
	btfss	STATUS,Z
	goto	khz
	movlw	'2'		;test if < 2
	subwf	DIGITS+1,w
	btfsc	STATUS,C
	goto	khz
	movlw	'0'		;test if 0
	xorwf	DIGITS+1,w
	btfss	STATUS,Z
	goto	xkhz1
	movlw	'0'		;test if 0
	xorwf	DIGITS+2,w
	btfsc	STATUS,Z
	goto	hz0
xkhz1
	movlw	TBUF		; Reset TBUF pointer
	movwf	TBUF_PTR
	movlw	0x04		;output first 4 characters
	movwf	count1
	movlw	DIGITS+0	;MSD of freq
	movwf	FSR
xkhz2
	movlw	'0'			;test if "0"
	xorwf	INDF,w
	btfss	STATUS,Z
	goto	xkhz3
	movlw	' '			;change preceeding "0's" to "space"
	call	tbuffer
	incf	FSR	,f
	decfsz	count1	,f
	goto	xkhz2
	goto	xkhz4
xkhz3
	movf	INDF,w
	call	tbuffer
	incf	FSR	,f
	decfsz	count1	,f
	goto	xkhz3
xkhz4
	movlw	'.'
	call	tbuffer
	movf	INDF,w		;output last 3 characters
	call	tbuffer
	incf	FSR	,f
	movf	INDF,w
	call	tbuffer
	incf	FSR	,f
	movf	INDF,w
	call	tbuffer
	movlw	' '
	call	tbuffer
	movlw	'k'
	call	tbuffer
	movlw	'H'
	call	tbuffer
	movlw	'z'
	call	tbuffer
	movlw	' '
	call	tbuffer
	movlw	' '
	call	tbuffer
	call	tbuffer_out
	goto	xkhz
;
hz
	movlw	0xC8		;1 sec gate
	movwf	gate
	call	count
	call	cnvt		;convert binary to BCD
	movlw	'0'			;test if "0"
	xorwf	DIGITS+0,w
	btfss	STATUS,Z
	goto	xkhz1
	movlw	'0'			;test if "0"
	xorwf	DIGITS+1,w
	btfss	STATUS,Z
	goto	xkhz1
	movlw	'2'			;test if < 2
	subwf	DIGITS+2,w
	btfsc	STATUS,C
	goto	xkhz1
hz0
	movlw	TBUF		; Reset TBUF pointer
	movwf	TBUF_PTR
	movlw	0x07		;output first 7 characters
	movwf	count1
	movlw	DIGITS+0		;MSD of freq
	movwf	FSR
hz1
	movlw	'0'		;test if "0"
	xorwf	INDF,w
	btfss	STATUS,Z
	goto	hz2
	movlw	' '		;change preceeding "0's" to "space"
	call	tbuffer
	incf	FSR	,f
	decfsz	count1	,f
	goto	hz1
	goto	hz3
hz2
	movf	INDF,w
	call	tbuffer
	incf	FSR	,f
	decfsz	count1	,f
	goto	hz2
hz3
	movlw	' '
	call	tbuffer
	movlw	'H'
	call	tbuffer
	movlw	'z'
	call	tbuffer
	movlw	' '
	call	tbuffer
	movlw	' '
	call	tbuffer
	movlw	' '
	call	tbuffer
	movlw	' '
	call	tbuffer
	call	tbuffer_out
	goto	hz
;
overflow
	IFNDEF NO_LCD
	movlw	0x01		;clear display
	call	lcd_inst_out
	ENDIF

	movlw	TBUF		; Reset TBUF pointer
	movwf	TBUF_PTR
	movlw	'O'
	call	tbuffer
	movlw	'v'
	call	tbuffer
	movlw	'e'
	call	tbuffer
	movlw	'r'
	call	tbuffer
	movlw	'f'
	call	tbuffer
	movlw	'l'
	call	tbuffer
	movlw	'o'
	call	tbuffer
	movlw	'w'
	call	tbuffer
	call	tbuffer_out

	IFNDEF NO_LCD
	movlw	0x02		;cursor at home
	call	lcd_inst_out
	ENDIF

	goto	mhz
;
	end

Comments: