;*****************************************************************************************
; Logging Frequency Counter, v1.0
; 
; This software runs on a standard and readily-available PSK31 Audio Beacon pc board.
; By loading this software into the chip you can change the behavior of the Beacon board
; to have it perform as a frequency counter that reports its measurements over the on-board
; RS-232C serial port to a computer running a terminal emulator program such a s PROCOMM,
; HyperTerminal, or equivalent. 
;
; A simple, single-stage transistor buffer/amp is used to condition the signal being measured
; before it is presented as a square wave to input port RB7. The high-to-low transitions of that
; signal are counted during a measurement period of 500ms. The resultant two-byte binary count
; is doubled (to get cycles/second, or Hertz), converted to BCD, and sent out the serial port
; to a terminal connected to pc board's RS-232 line.
;
; Upon powering up the board, an initial message is displayed on the terminal and three menu
; options are displayed. The operator can select (S)ingle Readings, (M)ultiple Readings, or 
; (C)ontinuous Readings.  
;
; If the "Single Readings" menu item is selected, the text string "Frequency = xxxxxx" is displayed, where the 
; xxxxxx is the 6-digit result, in Hertz, of the signal frequency measurement. A ">" character is
; then displayed on the next line to prompt the user for another menu selection (S, M, or C).
;
; If "Multiple Readings" menu item is selected, the text line and frequency measurement is 
; displayed as in the Single Reading option; however instead, 15 readings are autiomatically 
; done, resulting in 15 lines of "Frequency = xxxxxx" being displayed.  This mode is useful to
; determine if the signal being measure is relatively stable and unchanging. When the 15 readings
; are completed, the ">" charactr is displayed again to prompt the user for another menu
; selection.
;
; If the "Continuous Readings" menu item is selected, same operations occur as described above in 
; the Multiple Readings case, except that the readings and text line displays continue on
; forever.  This menu item is useful in cases when adjustments are being made to an oscillator, 
; for example, in that you can see the freqncy change on the terminal display while the physical
; adjustment is being made. In order to exit this Continuous mode, the Beacon board would need to 
; be reset by interrupting the power supply.
;
; The range of frequencies that can be reliably measured with this project is about 10 Hz to 300 kHz.
; The technique used is to count transitions occuring on bit 7 of port RB. Each transition causes an
; interrupt which vectors the program flow to the interrupt service routine (ISR) located at the 
; beginning of the source listing. A two-byte counter increments each time an interrupt occurs during
; the counting period. Although the SX processor is being clocked at 50 mHz, providing instruction 
; cycle times of only 20 ns, higher frequencies than 300 kHz will start affecting the accuracy of the
; measurements.  Above this rate, the time spent in the ISR starts affecting the measurements and the
; timing window should be adjusted to compensate.  This can be done by measuring a known frequency
; and adjusting the time interval until the correct reading is obtained.  With this type of calibration,
; frequencies up to 10 mHz can be measured. Note that once a calibration is made at one range of frequencies
; the counter can only be expected to deliver accurate results in that range.
;
; (A better way to handle event counting, and hence frequency measurements, would be to use the RTCC event
; counter pin on the SX chip.  This hardware method of counting nearly eliminates the instruction time 
; overhead occuring in the ISR, thus making the measurements accurate over the entire applicable range.
; However, I was unable to get the counter to work properly with overflows causing interrupts, so I 
; reverted to using the edge-triggered capabilities on the RB port input pins.)
;
; The time period can easily be changed in the software to perform the frequency measurements in 
; either a quicker or a longer interval. This would be useful to do in cases when faster readings or 
; greater precision is desirable. There is a simple delay routine that has an upper timer variable
; called "wait_hi" that can be adjusted in the source code to yield a "time gate" period different
; from the 500ms currently set.
;
; HARDWARE DESCRIPTION
; A signal conditioning circuit is required to square-up the sinewave signals typically measured with this
; project. The one-transistor circuit used here is used as a switch - copmpletely saturating, or turning
; on, during the positive swings of the input signal, and completely turning off during the negative swings.
; The resultant 0-to-5V signal is thus an ideal input for transition counting on pin 7 of port RB.
;
; Sensitivity of this single-stage conditioner is adequate for many levels of input signal. However for
; fairly low level input signals, another stage might be required in order to provide appropriate 
; amplification of the signal before it is squared-up for counting.
;
; Another factor relating to the range of frequencies being measured is the coupling capacitor in the
; signal conditioning circuit. As shown, a 0.1 uF capacitor will pass signals from about 300 Hz up to
; 300 kHz.  Above or below this range, the capacitor will present improper impedances and not provide
; enough of a signal to in the base of the transistor and it will not switch (saturate) enough or at all
; to produce the desired squared-up waveform to the RB7 input.  To measure signals below 300 Hz, the 
; coupling capacitor should be 1 uF.  For signals above 300 kHz, a .01 uF capacitor should be used.
;
; 
;
; 

;*****************************************************************************************
		device	SX28L,oscxt4,turbo,stackx_optionx	;SX-Key device directives
		freq	50_000_000		; default run speed = 50MHz
		id	'SXeval28'		; 
		reset	reset_entry		; set reset vector

;*****************************************************************************************
; Global Register definitions

		org     $08			;start of program registers

isr_temp	ds	1		; temp variable used by the ISR

flags		ds	1		; Flags to indicate various things
   timer_flag	equ	flags.0		; Indicates a 16-bit timer rollover
   rx_flag	equ	flags.1		; Indicates the reception of a byte from the UART 1
   counter_flag	equ	flags.2 	; Indicates counter mode (vs serial mode)

timer_lo	ds	1
timer_mid	ds	1
timer_hi	ds	1
count_lo	ds	1
count_hi	ds	1
temp		ds	1		; temp variable used in the following routines:
					; -get_hex
					; -get_valid_hex
					; -send_hex

;*****************************************************************************************
; Watch statements (to view variable in debug screen)

watch		count_lo,8,uhex
watch		count_hi,8,uhex

;*****************************************************************************************
; RAM Bank Register definitions

	; Bank 0
		org     $10
serial		=       $	;UART bank
tx_high		ds      1	;hi byte to transmit
tx_low		ds      1	;low byte to transmit
tx_count	ds      1	;number of bits sent
tx_divide	ds      1	;xmit timing (/16) counter
rx_count	ds      1	;number of bits received
rx_divide	ds      1	;receive timing counter
rx_byte		ds      1	;buffer for incoming byte
string		ds	1	;used by send_string to store the address in memory
byte		ds	1	;used by serial routines
hex		ds	1	;used in send_hex and get_hex routines
timers		=	$

	; Bank 1
		org	$30
bank1		=	$
loop_cntr	ds	1	;loop control for frequency readings

	; Bank 2
		org	$50
math		=	$
bin_number	ds	4
bcd_number	ds	5
count		ds	1
tempm		ds	1

	; Bank 3
		org	$70

	; Bank 4
		org	$90

	; Bank 5
		org	$b0

	; Bank 6
		org	$d0

	; Bank 7
		org	$f0

read_buffer	=	$

;*****************************************************************************************
; Port Assignment

RA_init		equ	%0001
RA_io		equ	%1010
RB_init		equ	%11111111
RB_io		equ	%11111111
RC_init		equ	%11111111
RC_io		equ	%11111111
RC_CMOS		equ	%10011111

;**************************************************************************
; System Equates

rx_pin		equ	ra.1	;UART receive input
tx_pin		equ	ra.0	;UART transmit output
WAIT_HI		equ	$40	;wait loop high byte
WAIT_LO		equ	$40	;wait loop low byte

;*****************************************************************************************
; UART Constants
; The following three values determine the UART baud rate.
; The value of baud_rate and int_period affect the baud rate as follows:
;  Baud rate = 50MHz/(baud_rate * int_period * RTCC_prescaler)
;
; Note: *int_period must <256 and longer than the length of the slowest possible
; interrupt sequence in instruction cycles. Changing the value of int_period will
; affect the rest of the virtual peripherals due to timing issues. The start delay
; value must be set equal to (baud_rate)*1.5 + 1
;*********************************************************************************

baud_rate_192	=	12			;for 19200 baud rate generation
start_delay_192	= 	baud_rate_192+baud_rate_192/2+1

int_period	equ	217			; RTCC interrupt rate

;*****************************************************************************************
; Interrupt Service Routine
;
; Note: The interrupt code must always originate at address $0.
;
; Interrupt Frequency = (Cycle Frequency / -(retiw value))  For example:
; With a retiw value of -217 and an oscillator frequency of 50MHz, this
; code runs every 4.34us.

		org     $0
interrupt					;
		mov	w,m			;Save the m register.
		mov	isr_temp,w		;

		sb	counter_flag		;if counter mode
		jmp	:transmit		;   go do serial processing
						;   else we're counting

		mode	$09			;reset the interrupt flag
		mov	!rb,%00000000
		incsz	count_lo		;increment low byte counter
		reti
		inc	count_hi		;if low passed 0, increment the high byte counter
		reti

	;*********************************************************************************
	; Virtual Peripheral: Universal Asynchronous Receiver Transmitter (UART) 
	; These routines send and receive RS232C serial data, and are currently
	; configured (though modifications can be made) for the popular
	; "No parity-checking, 8 data bit, 1 stop bit" (N,8,1) data format.
	; RECEIVING: The rx_flag is set high whenever a valid byte of data has been
	; received and it the calling routine's responsibility to reset this flag
	; once the incoming data has been collected.
	; TRANSMITTING: The transmit routine requires the data to be inverted
	; and loaded (tx_high+tx_low) register pair (with the inverted 8 data bits
	; stored in tx_high and tx_low bit 7 set high to act as a start bit). Then
	; the number of bits ready for transmission (10=1 start + 8 data + 1 stop)
	; must be loaded into the tx_count register. As soon as this latter is done,
	; the transmit routine immediately begins sending the data.
	; This routine has a varying execution rate and therefore should always be
	; placed after any timing-critical virtual peripherals such as timers,
	; adcs, pwms, etc.
	; Note: The transmit and receive routines are independent and either may be
	;	removed, if not needed, to reduce execution time and memory usage,
	;	as long as the initial "BANK serial" (common) instruction is kept.
	;
	;       Input variable(s) : tx_low, tx_high, tx_count
	;       Output variable(s) : rx_flag, rx_byte
	;       Variable(s) affected : tx_divide, rx_divide, rx_count
	;       Flag(s) affected : rx_flag

:transmit	bank	serial			;switch to serial register bank
		decsz	tx_divide		;only execute the transmit routine
		jmp	:receive		; 
		mov	w,#baud_rate_192	;load UART 1 baud rate
		mov	tx_divide,w		; 
		test    tx_count		;are we sending?
		snz				; 
		jmp	:receive		;
		clc                             ;yes, ready stop bit
		rr      tx_high			; and shift to next bit
		rr      tx_low			; 
		dec     tx_count		;decrement bit counter
		snb	tx_low.6		;output next bit
		clrb	tx_pin			; 
		sb	tx_low.6		; 
		setb	tx_pin			; 

:receive	sb	rx_pin			;get current rx bit
	        clc				; 
	        snb	rx_pin			; 
	        stc				; 
		test    rx_count		;currently receiving byte?
		sz				; 
		jmp	:rxbit			;if so, jump ahead
		mov     w,#9                    ;in case start, ready 9 bits
		sc                              ;skip ahead if not start bit
		mov     rx_count,w		;it is, so renew bit count
		mov     w,#start_delay_192	;ready 1.5 bit periods
		mov     rx_divide,w		; 
:rxbit		decsz	rx_divide		;middle of next bit?
          	jmp	:rxdone			;exit
		mov	w,#baud_rate_192	;yes, ready 1 bit period
		mov	rx_divide,w		; 
		dec     rx_count		;last bit?
		sz                              ;if not
		rr      rx_byte			; then save bit
		snz                             ;if so,
		setb    rx_flag			; then set flag
:rxdone						;else, exit

	; Set Interrupt Rate

isr_end		mov	w,isr_temp		;Restore the m register.
		mov	m,w			;
		mov	w,#-int_period		;refresh RTCC on return
		retiw				;return from the interrupt
						; = 1/(int_period*RTCC prescaler*1/50MHz)
						; = 1/(217*1*20ns) = 4.34us

	; End of the Interrupt Service Routine

;*****************************************************************************************
; RESET VECTOR -- Program execution begins here on power-up or after a reset

reset_entry		

	; Initialize all port configuration
		mov	w,#$0d
		mov	m,w
		mov	w,#RC_CMOS         	; Initialize CMOS for port C
		mov	!rc,w

		; Setup A & C port config
		mov	w,#$0f
		mov	m,w
		mov     w,#RA_init            	; Initialize data latches for port A
		mov     ra,w
		mov	w,#RA_io          	; Initialize DDIR for port A
		mov	!ra,w
		mov     w,#RC_init          	; Initialize data latches for port C
		mov     rc,w
		mov	w,#RC_io          	; Initialize DDIR for port C
		mov	!rc,w
		
		;setup Port B config
		mode 	$0F			;direction = input
		mov	!rb,#%11111111
		mode	$0C			;schmitt trigger
		mov	!rb,#%00000000
		mode	$0A			;wake-up on falling edge
		mov	!rb,#%11111111

	; Clear all Data RAM locations

:cont		clr	fsr			;reset all ram banks
:zero_ram	sb	fsr.4			;are we on low half of bank?
		setb	fsr.3			;If so, don't touch regs 0-7
		clr	ind			;clear using indirect addressing
		incsz	fsr			;repeat until done
		jmp	:zero_ram

		clrb	counter_flag		;not in counter mode to start (i.e., in serial mode)

	; Setup and enable RTCC interrupt, WREG register

		mov     !option,#%00011111      ;enable rtcc interrupt and wreg

;*****************************************************************************************
; MAIN PROGRAM CODE 
;*****************************************************************************************

main		mov	w,#_hello			; send title string
		call	@send_string

		; Main Program Loop

menu		mov	w,#_menu			; send the menu
		call	@send_string
send_prompt	mov	w,#_prompt			; send 'prompt' string
		call	@send_string

main_loop	call	@get_byte
		cje	byte,#13,send_prompt
		call	@uppercase			; set to uppercase
		cje	byte,#'S',single		; Single Reading
		cje	byte,#'M',multiple		; Multiple Readings
		cje	byte,#'C',continuous		; Continuous Readings
		jmp	main_loop

continuous	mov	w,#$FF				; continuous loops
		jmp	count_set
		
multiple	mov	w,#$0F				; multiple loops (15)
		jmp	count_set
	
single		mov	w,#1				; single loop

count_set	bank	bank1
		mov	loop_cntr,w

count_start	mov	w,#_frequency			; display "Frequency ="
		call	@send_string
		setb	counter_flag			; put program into "counting mode"
		clr	count_hi
		mov     !option,#%11011111       	; disable rtcc rollover interrupt (b6=1)
		mode	$0B				; wake-up enable RB7
		mov	!rb,#%01111111

		; wait 500ms second

wait		mov	timer_hi,#$5E
		clr	count_lo
		clr	count_hi
wait_2		mov	timer_mid,#$FD
wait_3		mov	timer_lo,#$FF
wait_4		decsz	timer_lo
		jmp	wait_4
		test	timer_mid
		jz	wait_5
		dec	timer_mid
		jmp	wait_3
wait_5		test	timer_hi
		jz	count_done
		dec	timer_hi
		jmp	wait_2
		
	
count_done	mode	$0B				; turn off RB7 interupts
		mov	!rb,#%11111111
		clrb	counter_flag			; put program back into "serial mode"

		; Compute Frequency

		bank	math
		mov	bin_number,count_lo
		mov	bin_number+1,count_hi
		clr	bin_number+2
		clr	bin_number+3
		clc					; count = count x 2
		rl	bin_number
		rl	bin_number+1
		rl	bin_number+2
		call	@bindec

		mov     !option,#%00011111      	; re-enable rtcc interrupt (b6=0) for serial
		mov	w,bcd_number+2
		call	@send_hex
		bank	math
		mov	w,bcd_number+1
		call	@send_hex
		bank	math
		mov	w,bcd_number
		call	@send_hex

		; Loop Control

		bank	bank1
		cje	loop_cntr,#$FF,count_start	; if = FF just loop forever
		decsz	loop_cntr			; else done with readings?
		jmp	count_start			; not yet
		jmp	send_prompt			; yep, all done. Go back to menu

org	$200
;*****************************************************************************************
; Jump table for second half of page

send_byte	jmp	send_byte_
get_byte	jmp	get_byte_
send_string	jmp	send_string_
send_hex	jmp	send_hex_
get_hex		jmp	get_hex_
get_valid_hex	jmp	get_valid_hex_
uppercase	jmp	uppercase_

;*****************************************************************************************
; Miscellaneous Subroutines


;*********************************************************************************
; String data (for RS-232 output) and tables needs to be in page $300
;*********************************************************************************

_hello	        dw      13,10,13,10,'LOGGING FREQUENCY COUNTER'
	       	dw      13,10,'     N2APB  v1.0 ',0
_prompt		dw	13,10,'>',0
_frequency	dw	13,10,'Frequency = ',0
_menu		dw	13,10,'S - Single Reading',13,10,'M - Multiple Readings',13,10,'C - Continuous Readings',13,10,0

;*********************************************************************************
; Function: hex_table
; Returns ASCII digit

hex_table	add	pc,w
		retw	'0'
		retw	'1'
		retw	'2'
		retw	'3'
		retw	'4'
		retw	'5'
		retw	'6'
		retw	'7'
		retw	'8'
		retw	'9'
		retw	'A'
		retw	'B'
		retw	'C'
		retw	'D'
		retw	'E'
		retw	'F'

org	$300
;*********************************************************************************
; Function: get_hex
;	This routine returns with an 8-bit value in the W and in the hex 
;	register.  It accepts a hex number from the terminal screen and
;	returns. Remember to write a prompt to the screen before calling get_hex
; OUTPUTS:
;	-received byte in rx_byte

get_hex_	bank	SERIAL
;		mov	w,#_enter_hex
;		page	send_string
;		call	send_string
		call	get_valid_hex
		mov	w,byte			; send the received (good) byte
		call	send_byte
		swap	temp			; put the nybble in the upper nybble of
		mov	w,temp
		mov	hex,w			; hex register

		call	get_valid_hex
		mov	w,byte			; send the second received byte
		call	send_byte
		mov	w,temp			
		and	w,#$0f
		or	w,hex
		mov	hex,w
		retp

get_valid_hex_
:gh1		clr	temp
		jnb	rx_flag,$		; get a byte from the terminal
		clrb	rx_flag
		mov	byte,rx_byte
		call	uppercase		; uppercase it.

:loop		mov	w,temp			; get the value at temp (index)
		call	hex_table
		xor	w,byte
		snz				; compare it to the received byte
		ret
		inc	temp			; if they are equal, we have the
		jb	temp.4,:gh1		; upper nybble.  Continue if not.
		jmp	:loop
		ret

;*********************************************************************************
; Function: send_hex
; Output a hex number
; INPUTS:
;	w	-	The byte to be output
; OUTPUTS:
; 	outputs the hex byte via RS-232

send_hex_	mov	temp,w
		swap	wreg
		and	w,#$0f
		call	hex_table
		call	send_byte
		mov	w,temp
		and	w,#$0f
		call	hex_table
		call	send_byte
		retp

;*********************************************************************************
; Function: send_byte
; Send byte via serial port
; INPUTS:
;	w 	-	The byte to be sent via RS-232
; OUTPUTS:
; 	outputs the byte via RS-232

send_byte_   	bank    serial

:wait        	test    tx_count		;wait for not busy
		sz
		jmp	:wait                   ;
		not     w                       ;ready bits (inverse logic)
		mov     tx_high,w		; store data byte
		setb    tx_low.7		; set up start bit
		mov     w,#10			;1 start + 8 data + 1 stop bit
		mov     tx_count,w
		retp                            ;leave and fix page bits

;*********************************************************************************
; Function: get_byte
; Get byte via serial port and echo it back to the serial port
; INPUTS:
;	-NONE
; OUTPUTS:
;	-received byte in rx_byte

get_byte_	sb	rx_flag			;wait till byte is received
		jmp	$-1
		clrb    rx_flag			;reset the receive flag
		bank	serial
		mov     w,rx_byte		;store byte (copy using W)
		mov     byte,w			; & fall through to echo char back
		retp

;*********************************************************************************
; Function: send_string
; Send string pointed to by address in W register
; INPUTS:
;	w	-	The address of a null-terminated string in program
;			memory
; OUTPUTS:
; 	outputs the string via RS-232

send_string_	bank	serial
 		mov     string,w		;store string address
:loop        	mov     w,string		;read next string character
		mov     m,#2                    ; with indirect addressing
		iread                           ; using the mode register
		mov     m,#$0f                   ;reset the mode register
		test    w                       ;are we at the last char?
		snz                             ;if not=0, skip ahead
		retp				;yes, leave & fix page bits
		call    send_byte               ;not 0, so send character
		inc     string			;point to next character
		jmp     :loop                   ;loop until done

;*********************************************************************************
; Function: uppercase
; Convert byte to uppercase.
; INPUTS:
;	byte - the byte to be converted
; OUTPUTS:
; 	byte - converted byte

uppercase_    	mov	w,#'a'            	;if byte is lowercase, then skip ahead
	    	mov	w,byte-w
	    	sc
		retp
		mov	w,#'a'-'A'		;change byte to uppercase
		sub	byte,w
		retp				;leave and fix page bits



;*********************************************************************************
org	$400
;*********************************************************************************
		; 32 bit binary to BCD conversion
		; entry: 32 bit binary number in $10-13
		; exit: 10 digit BCD number in $14-18
		; algorithm= shift the bits of binary number into the BCD number and decimal
		; 	     correct on the way
bindec		mov	count,#32
		mov	fsr,#bcd_number		; points to the BCD result
		
clr_bcd		clr	ind			; clear BCD number
		snb	fsr.3			; reached $18?
		jmp	shift_both		; yes, begin algorithm
		inc	fsr			; no, continue on next byte
		jmp	clr_bcd			; loop to clear
		
shift_both	mov	fsr,#bin_number		; points to the binary number input
		clc				; clear carry, prepare for shifting
		
shift_loop	rl 	ind			; shift the number left
		snb	fsr.3			; reached $18? (finish shifting both numbers)
		jmp	check_adj		; yes, check if end of everything
		inc	fsr			; no, next byte
		jmp	shift_loop		; not yet

check_adj	decsz	count			; end of 32 bit operation?
		jmp	bcd_adj			; no, do bcd adj
		retp

bcd_adj		mov	fsr,#bcd_number		; points to first byte of the BCD result

bcd_adj_loop	call	digit_adj		; decimal adjust
		snb	fsr.3			; reached last byte?
		jmp	shift_both		; yes, go to shift both number left again
		inc	fsr			; no, next byte
		jmp	bcd_adj_loop		; looping for decimal adjust

		; prepare for next shift    
		; 0000  --> 0000	0 -->0
		; 0001	--> 0010	1 -->2
		; 0010 	--> 0100	2 -->4
		; 0011	--> 0110	3 -->6
		; 0100	--> 1000	4 -->8
		; 0101	--> 1010	5 -->A, correct result is 10, so need to add 3
		;			so that 5+3=8, and 1000 will be shifted to be 1 0000
		; the same is true for 6-9
digit_adj	; consider LSD first
		mov	w,#3			; 3 will become 6 on next shift
		add	w,ind			; which is the decimal correct factor to be added
		mov	tempm,w
		snb	tempm.3			; > 7? if bit 3 not set, then must be <=7, no adj.
		mov	ind,w			; yes, decimal adjust needed, so store it
		
		; now for the MSD
		mov	w,#$30			; 3 for MSD is $30
		add	w,ind			; add for testing
		mov	tempm,w
		snb	tempm.7			; > 7?
		mov	ind,w			; yes, store it
		
		ret


END		;End of program code


