; fcount.asm
;
; Code to the frequency counter project by Robert Östling
; http://www.robos.org/
; robert@robos.org
;
; Ideas "borrowed" from a project by Simone Benvenuti and
; Andrea Geniola.
;
; Clock speed:			3,686,400 Hz
; Instructions per second:	921,600
;
; One sample can contain up to 2^16 cycles, with a maximum
; frequency of about 16MHz (2^24Hz) one sample should take
; 1/256 second or 3600 instruction cycles. Sampling is done
; in a 240*15 instruction loop, during which portb<0> is
; configured as an high-impedance input pin, allowing
; porta<4> to detect the signal. The rest of the time,
; portb<0> is a low-impedance output pin, essentialy
; grounding porta<4>
;
; If the frequency turns out to be below 2^16 Hz, the sample
; time is increased to 1 second, giving a 1Hz resolution. If
; the frequency is between 2^16 and 2^20 Hz, 1/16 second
; samples are used, giving a 16Hz resolution. These different
; modes can be recognized by the blinking speed of the display.
; In 2^24Hz mode, there is no blinking. In 2^20Hz mode, the
; frequency is 1Hz and in 2^16Hz mode it is 0.5Hz.
;
; portb<1-7> are connected to the LED segments as follows:
;
;     1
;    ___
; 6 |   | 2
;   |___|
; 5 | 7 | 3
;   |___|
;
;     4
;
; Pin 0 of portb is connected directly to pin 4 of porta,
; this pair of pins is connected to the input signal via
; a 470R resistor.
;
; porta<0-3> bits control which LED display that is active.
; Bit 3 controls the LSD and bit 0 the MSD. This is all due
; to a constructing error, originally bit 0 controlled the
; LSD. A _high_ level on the pin activates the segment in
; question.


	list p=pic16f84a
	#include <p16f84a.inc>

	__config _cp_off & _pwrte_on & _wdt_off & _xt_osc

	cblock	0x0c
	cnt,cnt2,clow,chigh,dispcnt
	endc

	org 0
reset:
	clrf	intcon			; disable all interrupts
	clrf	porta
	clrf	portb			; set all outputs to 0
	bsf	status,rp0		; bank 1
	movlw	0x27			; external timer with prescaler 256
	movwf	option_reg		; trigger on rising edge
	movlw	0x10
	movwf	trisa			; porta<0-3> output, porta<4> input
	clrf	trisb			; portb<0-7> output
	bcf	status,rp0		; bank 0

main_loop:
	call	sample			; count the number of cycles in 1/256 second
	call	cycles			; move this number to chigh:clow
	movfw	chigh
	andlw	0xff			; is the frequency below 2^16 Hz?
	btfsc	status,z
	goto	do_1sec			; yes, count the number of cycles in 1 second
	movfw	chigh
	andlw	0xf0			; is the frequency below 2^20 Hz?
	btfsc	status,z
	goto	do_16sec		; yes, count the number of cycles in 1/16 second

do_256sec:
	call	display			; update display
	goto	main_loop

do_16sec:
	call	mid_sample		; 1/16s sample
	call	cycles			; move the sample to chigh:clow
	call	delay102399
	call	delay102399
	call	delay102399
	call	delay102399		; turn off the display for about 450ms
	call	display			; update display
	goto	main_loop

do_1sec:
	call	long_sample		; 1s sample
	call	cycles			; move the sample to chigh:clow
	call	display			; update display
	call	display			; hold for a little bit longer (about 1s)
	goto	main_loop

sample:
	clrf	tmr0			; reset timer
	bsf	status,rp0		; bank 1
	movlw	0x01
	movwf	trisb			; start collecting cycles!
	bcf	status,rp0		; bank 0
	movlw	0xef
	movwf	cnt			; cnt = 239
sample_loop:
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	decfsz	cnt
	goto	sample_loop		; 15 cycle loop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	bsf	status,rp0
	clrf	trisb			; another 15 cycles, then stop sampling.
	bcf	status,rp0
	return

mid_sample:
	clrf	tmr0			; reset timer
	bsf	status,rp0		; bank 1
	movlw	0x01
	movwf	trisb
	bcf	status,rp0		; bank 0, also start sampling
	movlw	0x2b			; |
	movwf	cnt2			; | 3 cycles so far
mid_sample_loop:
	call	delay1276		; |
	nop				; |
	decfsz	cnt2			; |
	goto	mid_sample_loop		; | 43*1280 cycles
	call	delay1276		; 1276 cycles
	call	delay1276		; 1276 cycles
	nop				; |
	nop				; | 3 cycles
	nop				; |
	bsf	status,rp0		; | 2 more cycles
	clrf	trisb			; stop sampling
	bcf	status,rp0
	return

delay1276:				; call delay1276 <-- 1 cycle
	movlw	0xfe			; |
	movwf	cnt			; | 2 cycles
delay1276_loop:
	nop				; |
	nop				; |
	decfsz	cnt			; | 5*254 cycles
	goto	delay1276_loop		; |
	nop				; |
	nop				; | 3 cycles
	return				; |

long_sample:
	clrf	tmr0			; reset timer
	bsf	status,rp0		; bank 1
	movlw	0x01
	movwf	trisb
	bcf	status,rp0		; bank 0, also start sampling
	call	delay102399
	call	delay102399
	call	delay102399
	call	delay102399
	call	delay102399
	call	delay102399
	call	delay102399
	call	delay102399
	call	delay102399
	nop
	nop
	nop
	nop
	nop
	nop
	bsf	status,rp0
	clrf	trisb			; stop sampling
	bcf	status,rp0
	return

delay102399:				; call delaydelay102399 <-- 1 cycle
	movlw	0x63			; |
	movwf	cnt2			; | 2 cycles
delaydelay102399_loop:
	call	delay1016		; |
	nop				; |
	nop				; |
	nop				; |
	nop				; | 99*1024 cycles
	nop				; |
	decfsz	cnt2			; |
	goto	delaydelay102399_loop	; |
	call	delay1016		; 1016 cycles
	nop				; |
	nop				; | 3 cycles
	nop				; |
	return				; 1 cycle

delay1016:				; call delay1016 <-- 1 cycle
	movlw	0xfd			; |
	movwf	cnt			; | 2 cycles
delay1016_loop:
	nop				; |
	decfsz	cnt			; | 253*4 cycles
	goto	delay1016_loop		; |
	return				; 1 cycle

cycles:
	movfw	tmr0
	movwf	chigh			; high byte = counter
	clrf	cnt			; will become prescaler count
cycles_loop:
	bcf	portb,0
	bsf	portb,0
	bcf	portb,0			; pulse to porta<4> (and increase prescaler)
	incf	cnt
	movfw	chigh
	xorwf	tmr0,w			; did we overflow the prescaler?
	btfsc	status,z
	goto	cycles_loop		; no, send another pulse
	movlw	0xff
	movwf	clow
	movfw	cnt
	subwf	clow
	incf	clow			; clow = prescaler
	return

display:
	movlw	0x30
	movwf	dispcnt			; display 0x30 times (about 500ms)
display_again:
	movfw	clow
	andlw	0x0f			; low 4 bits of byte
	call	digit			; get its hex digit
	movwf	portb			; output to LED segments
	bsf	porta,3			; enable first digit
	call	delay			; wait 40ms...
	bcf	porta,3			; then disable it.

	swapf	clow,w
	andlw	0x0f			; high 4 bits of byte
	call	digit			; get its hex digit
	movwf	portb			; output to LED segments
	bsf	porta,2			; enable second digit
	call	delay			; wait 40ms...
	bcf	porta,2			; then disable it.

	movfw	chigh
	andlw	0x0f			; low 4 bits of byte
	call	digit			; get its hex digit
	movwf	portb			; output to LED segments
	bsf	porta,1			; enable third digit
	call	delay			; wait 40ms...
	bcf	porta,1			; then disable it.

	swapf	chigh,w
	andlw	0x0f			; high 4 bits of byte
	call	digit			; get its hex digit
	movwf	portb			; output to LED segments
	bsf	porta,0			; enable fourth digit
	call	delay			; wait 40ms...
	bcf	porta,0			; then disable it.
	clrf	portb			; clear portb

	decfsz	dispcnt
	goto	display_again
	return

; Runs for about 2.5ms.
delay:
	movlw	0x03
	movwf	cnt
delay_loop1:
	clrf	cnt2
delay_loop2:
	decfsz	cnt2
	goto	delay_loop2
	decfsz	cnt
	goto	delay_loop1
	return

; w = digit, return code in w
digit:
	addwf	pcl
	retlw	0x80	; 0
	retlw	0xf2	; 1
	retlw	0x48	; 2
	retlw	0x60	; 3
	retlw	0x32	; 4
	retlw	0x24	; 5
	retlw	0x04	; 6
	retlw	0xf0	; 7
	retlw	0x00	; 8
	retlw	0x20	; 9
	retlw	0x10	; A
	retlw	0x06	; b
	retlw	0x8c	; C
	retlw	0x42	; d
	retlw	0x0c	; E
	retlw	0x1c	; F

	end

