; COPYRIGHT 2002 BY DAVID EK. PERMISSION IS GRANTED TO FREELY USE
; THIS SOFTWARE FOR NONCOMMERCIAL PURPOSES.
;
; NOTE: USE AT YOUR OWN RISK. THE AUTHOR ASSUMES NO LIABILITY FOR DAMAGE
; INCURRED FROM THE USE OF THIS SOFTWARE.
;
; See QRP Quarterly Summer 2001 issue for a complete circuit description.
; ADDENDUM: since publication, the circuit has been modified to allow 
; connection of a paddle for hand sending. On the PIC, pin RB2 (pin 8)
; connects to the dit paddle, and pin RB3 (pin 9) connects to the dah
; paddle. Both these pins should be tied to +5V via a 10K resistor, and
; the paddle lines should be connected directly to the pins so that
; depressing either paddle grounds the corresponding pin. Note that
; depressing either paddle will stop any sending currently being done
; by the computer.
;
; PIC16F84 code for the Serial CW Sender project. The purpose
; of this project is to create a device which will receive ASCII strings
; via the serial port from a PC and cause the strings to be sent on a ham
; radio by keying the radio appropriately.
;
; The hardware circuit makes use of two major components: the 
; PIC16F84 itself and a MAX232 TTL-RS232 level converter.
; The MAX232 converts the TTL signals from the PIC to RS232 levels
; for the PC (and vice versa).
;
; The PIC16F84 pin assignments are as follows:
;
; RA1: serial out to PC
; RA2: CW out to rig key jack
; RA3: inverted CW out (inverted RA2) to drive LED if desired
; RB0: serial in from PC (generates interrupt)
; RB2: dit paddle connection
; RB3: dah paddle connection
;
; Written using Microchip MPLAB v5.40
;
; Please direct any questions, comments, or suggestions to the
; author: David Ek
;         ekdave@earthlink.net
;
; References:
; - the method for representing the CW characters in memory was adapted from
;   KEYER1.ASM by W. Buescher (DL4YHF). See www.qsl.net/dl4yhf/pic_key.html
; - the logic for handling iambic paddle keying was adapted from K1EL's K8
;   keyer source code. See http://www.k1el.com/.
;
; History:
;
; Rev 1 (17 Mar 2001):
;    Creation. Serial routines stolen from my DSC project.
; Rev 2 (9 Sept 2001):
;    Shortened the length of the space from three dahs to two
;    dahs and a dit. Changed version to 1.01. Changed version
;    string to have new call sign.
; Rev 3 (16 Jan 2002):
;    Added manual keyer function. Paddle can now be connected for
;    manual (non-computer) keying. Depressing a paddle key will
;    interrupt any computer sending. Dits are sent by grounding
;    RB2 (PIC pin 8), dahs by grounding RB3 (PIC pin 9). Changed
;    version to 1.10. Updated the comments. Rearranged the code
;    slightly to create seperate subroutines for sending dits and
;    dahs, so they could be called either by the routines that
;    send computer CW or the routines that handle manual keying.
; Rev 4 (5 Feb 2002):
;    Added Curtis Mode A and B. Select Mode A by grounding PIC
;    pin 7 (RB1). Select Mode B by tying pin 7 high using a 10K
;    resistor. Changed version to 1.15.
; Rev 5 (9 Mar 2002):
;    Fixed up Curtis Mode A (A's and N's not sent correctly).
;    changed version to 1.16.
; Rev 6 (16 Mar 2002):
;    More work on the paddle handling. Needed to enhance the logic
;    for cases where the opposite paddle was pressed while a 
;    dit/dah was being sent. Changed version to 1.17.
; Rev 7 (29 Mar 2002):
;    Added inverted CW out to RA3 to optionally drive an LED CW
;    indicator. Connect other end of LED to +5V through a 10K
;    resistor, so that the LED lights when RA3 is taken low.
;    Changed to version 1.18.
; Rev 8 (26 Sep 2002):
;    Modified Mainloop slightly--replaced the call to GetAChar
;    with a statement to clear the Received Char flag in
;    SerialRegs, since I was already checking to see whether a
;    character was there before making the call to GetAChar. For
;    some reason, when the paddle capabilities were added, the
;    sender would occasionally miss a command to send. This seems
;    to fix it, but I'm not sure why. Changed version to 1.19.
;
;----------------------------------------------------------------
;
; Description of communications protocol (NOTE: double quotes are
; used in the description to set off the characters to send or be
; received, but are not part of the protocol):
;
; 1) All communication is 9600/8/1/N with no flow control.
;
; 2) To get the version of the software, send "^". 
;    Returned will be a string terminated by a CR.
;
; 3) A string of characters to send must not exceed (currently) 54
;    bytes and must be prefaced with "<" and end with a carriage
;    return (ASCII 13). When finished sending, the interfacw will
;    send back "r".
;
; 4) To interrupt sending, send any character. Wait for the interface
;    to acknowledge by sending back "r" before sending more commands.
;
; 5) To set the speed send ">" followed by two unsigned bytes. The first
;    byte is the dit length in milliseconds for characters. The second
;    byte is the dit length in milliseconds for intercharacter spacing
;    (to allow Farnsworth-style spacing). The value of the second byte
;    should be greater than or equal to that of the first byte, or else
;    the space between letters will be too short. NOTE: timing here is
;    approximate, not perfect.
;
;    EXAMPLE: to set character dit length to 40 ms, and character space
;    dit length to 50 ms, send ">(2" because "(" is the character whose ASCII
;    value is 40, and "2" is the character whose ASCII value is 50.  
;
; 6) The following characters are supported for CW:
; 
;     A through Z (upper or lower case)
;     0 through 9
;     period (.)
;     comma (,)
;     slash (/)
;     BT prosign (=)
;     AR prosign (+)
;     question mark (?)
;     SK prosign (*)
;     KN prosign (:)
;     AS prosign (-)
;
;--------------------------------------------------------------------

	list	p=16f84
	radix	dec
	__config _CP_OFF & _WDT_OFF & _XT_OSC

	include	"p16f84.inc"

; defines:

; these are constants used for sending the CW characters. The MSB indicates
; whether this is a space/special character (if set) or a regular character
; (if clear). Spaces are currently the only special character, and if the MSB
; is set a space will be sent. If all bits are set, the character is not
; supported in the CW character set and will be skipped.
;
; If the MSB is clear, then the remaining bits
; indicate the dots and dashes to be sent. The bits are sent in order from most
; to least significant, with 1 meaning dash and 0 meaning dot. A leading 1 is
; required as a start bit (and is not sent as a dash) to indicate the start
; of the dots and dashes (since not all characters have the same number of 
; dots and dashes). The LSB is the last dot/dash in the string.
;
; NOTE: This method of representing the CW characters in memory was adapted from
; KEYER1.ASM (revision 3/2000), written by Wolfgang Buescher (DL4YHF).

_Char_Space	equ	b'10000000'	; space
_Char_Ignore	equ	b'11111111'	; skip this character
_Char_Period	equ	b'01010101'	; period
_Char_Comma	equ	b'01110011'	; comma
_Char_Slash	equ	b'00110010'	; slash
_Char_Equals	equ	b'00110001'	; equals, or BT
_Char_Plus	equ	b'00101010'	; plus, or AR
_Char_Question	equ	b'01001100'	; question mark
_Char_Asterisk	equ	b'01000101'	; SK
_Char_KN	equ	b'00110110'	; KN
_Char_AS	equ	b'00101000'	; AS (wait)
_Char_0		equ	b'00111111'	; zero
_Char_1		equ	b'00101111'	; one
_Char_2		equ	b'00100111'	; two
_Char_3		equ	b'00100011'	; three
_Char_4		equ	b'00100001'	; four
_Char_5		equ	b'00100000'	; five
_Char_6		equ	b'00110000'	; six
_Char_7		equ	b'00111000'	; seven
_Char_8		equ	b'00111100'	; eight
_Char_9		equ	b'00111110'	; nine
_Char_A		equ	b'00000101'	; A
_Char_B		equ	b'00011000'	; B
_Char_C		equ	b'00011010'	; C
_Char_D		equ	b'00001100'	; D
_Char_E		equ	b'00000010'	; E
_Char_F		equ	b'00010010'	; F
_Char_G		equ	b'00001110'	; G
_Char_H		equ	b'00010000'	; H
_Char_I		equ	b'00000100'	; I
_Char_J		equ	b'00010111'	; J
_Char_K		equ	b'00001101'	; K
_Char_L		equ	b'00010100'	; L
_Char_M		equ	b'00000111'	; M
_Char_N		equ	b'00000110'	; N
_Char_O		equ	b'00001111'	; O
_Char_P		equ	b'00010110'	; P
_Char_Q		equ	b'00011101'	; Q
_Char_R		equ	b'00001010'	; R
_Char_S		equ	b'00001000'	; S
_Char_T		equ	b'00000011'	; T
_Char_U		equ	b'00001001'	; U
_Char_V		equ	b'00010001'	; V
_Char_W		equ	b'00001011'	; W
_Char_X		equ	b'00011001'	; X
_Char_Y		equ	b'00011011'	; Y
_Char_Z		equ	b'00011100'	; Z

; These are used by the serial comm routines for timing. Note that
; slower speeds may not work well because the delays might be larger
; than 255, requiring the use of the prescalar.

_Clkspd		equ	1000000			;external clock / 4
_BaudRate	equ	9600			;desired baud rate
_Period		equ	(_Clkspd/_BaudRate)	;clock cycles / bit

_StartRxDelay	equ	(_Period/2 - 15)/3	;this is how long to
						;wait to get to the
						;middle of the start
						;bit. This is loops,
						;not clock cycles.

_BitRxDelay	equ	277 - _Period		;this is what to load
						;into TMR0 for correct
						;interval between bits
						;on RX

_BitTxDelay	equ	285 - _Period		;this is what to load
						;into TMR0 for correct
						;interval between bits
						;on TX

_StopTxDelay	equ	272 - _Period		;this is what to load
						;into TMR0 for correct
						;interval betweeen last
						;bit and stop bit on TX


; constants for timing the CW:

_MSDelay	equ	(_Clkspd/10000)		;number of times to execute the
						;delay loop to get a MS delay.
						;assumes 10 clock cycles per
						;trip through the delay loop.

; constants for PORTA:

_TX		equ	1			;serial output is PORTA,1
_CW		equ	2			;CW out is PORTA,2
_INVCW		equ	3			;inverted CW out is PORTA,3

; constants for PORTB:

_RX		equ	0			;PORTB,0 receives the serial input
_Mode		equ	1			;mode switch is PORTB,1
_Dit		equ	2			;dit paddle is PORTB,2
_Dah		equ	3			;dah paddle is PORTB,3

; constants for paddle keyer (used in KeyerFlags):

_DitPressed	equ	0			;if bit 0 set, dit paddle was
						;pressed

_DahPressed	equ	1			;if bit 1 set, dah paddle was
						;pressed

_BothPressed	equ	2			;if bit 2 set, both paddles are
						;pressed simultaneously

_DitInLast	equ	3			;if bit 3 set, last paddle pressed
						;was a dit (else a dah)

_DitOutLast	equ	4			;if bit 4 set, last thing sent was
						;a dit (else a dah)

; memory locations used by serial send/receive routines:

	cblock 0x0C

	BitCount	;number of bits left to send or
			;receive, not including start & stop
			;bits

	RXChar		;received character while being received
	RXBuff		;most recently received character

	TXChar		;character to transmit

	SerialReg	;status register:
			;bit 0: on if character has been
			;       received
			;bit 1: on if busy with RX/TX
			;bit 2: on if sending, off if receiving
			;bit 3: on if next bit to send is stop bit

	WSave		;copy of W register

	SSave		;copy of the Status register

; memory locations used by CW sending and timing:

	MSDelay		;holds loop counter for MS delay loop.

	DitLen		;milliseconds per dit.

	DitWordLen	;milliseconds per dit for intercharacter/word spacing

	DitLoopIndex	;loop index for WaitDit.

	BufCount	;number of unsent characters in the buffer

	CharToSend	;CW character being sent

	CharBitCount	;counter for bits in the CW character to send

	KeyerFlags	;flags for manual keyer:
			;bit 0: on means dit paddle was pressed
			;bit 1: on means dah paddle was pressed
			;bit 2: on means both paddles pressed simultaneously
			;bit 3: on means last thing sent was a dit (off means dah)
			;bit 4-7: not used

	CharBuf		;beginning of the buffer which holds characters to send

; DO NOT PUT ANY MORE VARIABLES AFTER CharBuf--THE CODE ASSUMES THAT ALL RAM AFTER
; CharBuf IS FOR THE CHARACTER BUFFER. IF YOU NEED TO ADD MORE VARIABLES, ADD THEM
; BEFORE CharBuf.

	endc

; more constants to set size of character buffer:

_MaxChars	equ	(0x4F-CharBuf+1)	;max characters to hold in charbuf

	org	0x00
	goto	Main

	org	0x04

;--------------------------------------------------------------------
;-----Serial Communication Routines----------------------------------
;--------------------------------------------------------------------
;
; The serial comm routines generate 1 start bit, 8 data bits, 1 stop bit, 
; no parity. The baud rate is determined by the delay programmed
; into the onboard timer. The sending and receiving is interrupt driven,
; meaning other tasks can be carried on while the characters are being
; sent and received.
;
;-----Main Interrupt Routine-----------------------------------------
;
; for timing: 4 cycles from interrupt time to get here.

; Save the W and STATUS registers:

Int
	movwf	WSave
	swapf	STATUS,W	;use swapf to prevent any
	movwf	SSave		;status flags from being changed

; cycles so far since interrupt occurred: 7

; Okay, I'm doing something goofy here. Instead of actually checking
; the overflow bits themselves, I'm using the enable bits to determine
; which interrupt occurred. I can do this because there should only
; ever be one type of interrupt active at a time. The problem with
; checking the overflow bits is that the T0IF bit can get set even
; if that interrupt is disabled.

; Check first for a timer overflow interrupt. The overflow bit gets
; set even if the interrupt is disabled.

	btfsc	INTCON,T0IE
	goto	DoBit		;we're in the middle of sending or
				;receiving

; 10 cycles executed on entry to DoBit

; If not a timer overflow interrupt, check for external interrupt:

	btfsc	INTCON,INTE	;RB0 is our receive line and it
	goto	StartRX		;generates an interrupt on a high-
				;to-low transition

; 12 cycles executed on entry to StartRX

; Else, must be something we don't care about:

	;do nothing for now

; Restore the W and STATUS registers:

Restore	swapf	SSave,W
	movwf	STATUS
	swapf	WSave,F
	swapf	WSave,W

	retfie

;------end Main Interrupt Routine------------------------------------


;------Subroutine SerSetup-------------------------------------------

SerSetup

; set up the option register for internal counting, WDT disabled,
; no prescaler.

	clrf	TMR0
	bsf	STATUS,RP0
	clrwdt			;set bits in OPTION_REG to
	movlw	b'10001000'	;enable internal clock counting,
	movwf	OPTION_REG	;disable watchdog timer.
	bcf	STATUS,RP0	;switch to bank 0

; set the output line to idle (high)

	bsf	PORTA,_TX	;set the output line to idle
				;(high) state

	movlw	b'00000001'
	movwf	PORTB

; enable the external interrupt via RB0

	movlw	b'10010000'	;set bits in INTCON to enable
	movwf	INTCON		;external interrupt

; initialize the SerialReg:

	clrf	SerialReg

	return

;------end SetSetup--------------------------------------------------

;------Subroutine StartRX--------------------------------------------
;
; This subroutine is called by the main interrupt routine when an
; external interrupt on RB0 occurs. This means we're receiving the
; start bit for a character. We want to enable the external TMR0
; interrupt and prepare to receive the character.

StartRX

; wait halfway through the bit to see if it's real:

	bcf	INTCON,INTF	;clear the interrupt
	movlw	_StartRxDelay
	movwf	BitCount	;this is the 15th instruction since
				;the interrupt. Note--we're using
				;BitCount for this loop purely for
				;convenience. Usually it's used to
				;actually count the bits we TX/RX.

RXWait	decfsz	BitCount,F	;this loop takes 3 times the initial
	goto	RXWait		;value of BitCount clock cycles
	
; now we should be at the middle of the start bit. Is the input still
; low? If not, goto Restore and ignore this interrupt.

	btfsc	PORTB,_RX
	goto	Restore

; if we get to here it must really be the start bit. Load TMR0,
; disable the external interrupt, and enable the TMR0 interrupt. 

; load up the appropriate delay to get us to the middle of the
; first bit:

	movlw	_BitRxDelay
	movwf	TMR0		;4 cycles from read of PORTB
	movlw	b'00100000'
	movwf	INTCON

; set the SerialReg to indicate that the routines are busy getting
; a character:

	movlw	b'00000010'
	movwf	SerialReg

; initialize BitCount:

	movlw	8
	movwf	BitCount

; okay, now we return.

	goto	Restore

;------end StartRX---------------------------------------------------


;------DoBit---------------------------------------------------------
;
; sends or receives the next bit. Bits are sent/received from least
; to most significant bit.

DoBit

; clear the TMR0 overflow interrupt flag:

	bcf	INTCON,T0IF

; Are we receiving?

	btfsc	SerialReg,2
	goto	Sending

; check to see if we're receiving the stop bit:

	movf	BitCount,F
	btfsc	STATUS,Z
	goto	GetStopBit

; if we get to here, we're in the middle of receiving. Get the next
; bit: (16 cycles to get to the next instruction from the start of
; the interrupt).

	rrf	PORTB,W		;rrf PORTB into W. This sets
				;the carry bit if RB0 was high.
	rrf	RXChar,F	;doing a rrf on RXChar brings
				;in the carry bit to the MSB.

; Decrement the bit counter.

	decf	BitCount,F

; reload TMR0 for the next interrupt, and
; go to the end of the interrupt routine.

	movlw	_BitRxDelay	
	movwf	TMR0		;21 cycles from start of interrupt
	goto	Restore

; if we get to here it's because we need to check for the stop bit.

GetStopBit
	btfss	PORTB,_RX	;is the RX line low? If so, it's not
	goto	Done		;the stop bit. Otherwise, set the
	movlw	b'00000001'	;SerialReg to show a character has
	movwf	SerialReg	;been received
	movf	RXChar,W	;copy the received character to RXBuff
	movwf	RXBuff
	goto	Done
		
; We got here because we're sending.
; check to see if we're finished sending the stop bit:

Sending
	btfsc	SerialReg,3
	goto	Done

; check to see if we need to send the stop bit:

	movf	BitCount,F
	btfsc	STATUS,Z	;18th cycle
	goto	SendStopBit

; if we get to here, we're in the middle of sending. Send the next
; bit: (16 cycles to get to the next instruction from the start of
; the interrupt).

	rrf	TXChar,F	;doing rrf on TXChar puts the
	btfss	STATUS,C	;least significant bit in the
	goto	SendZero	;carry flag.
	nop
	bsf	PORTA,_TX	;if carry is set, send a one.
	goto	EndDoBit	;PORTA,_TX is set on the 24th cycle

SendZero
	bcf	PORTA,_TX	;otherwise, send a zero. (24th cycle)
	nop			;nop's are for taking the same time
	nop			;to get to reloading TMR0 as for when
				;a one is sent.

; Decrement the bit counter.

EndDoBit
	decf	BitCount,F

; reload TMR0 for the next interrupt, and
; go to the end of the interrupt routine.

	movlw	_BitTxDelay	
	movwf	TMR0		;29th cycle
	goto	Restore


; Here we need to send the stop bit, turn off the TMR0 interrupt,
; turn on the external interrupt, and set the SerStatus register
; flags appropriately.

SendStopBit
	nop
	nop
	nop
	bsf	PORTA,_TX		;no. Send the stop bit. (24th cycle)
	bsf	SerialReg,3	;set the "sending stop bit" flag

; reload TMR0 for the next interrupt, and
; go to the end of the interrupt routine.

	movlw	_StopTxDelay
	movwf	TMR0		;27th cycle
	goto	Restore

; we're completely done sending or receiving. Clean up.

Done	movlw	b'00010000'	;set bits in INTCON to enable
	movwf	INTCON		;external interrupt
	movlw	b'00000001'
	andwf	SerialReg,F	;clear the busy bits in SerialReg
	goto	Restore

;------end DoBit-----------------------------------------------------

;------Subroutine SendChar-------------------------------------------
;
; This is not called by the interrupt handler. Rather, it activates 
; the interrupts needed to send it. Put the character to be sent in
; the TXChar file register before calling this subroutine.
;

SendChar
	
; send the start bit:

	bcf	PORTA,_TX

; set the SerStatus to indicate that the routines are busy sending
; a character:

	movlw	b'00000110'
	movwf	SerialReg

; load up TMR0 so it overflows at the right time.

	nop			;for timing
	movlw	_BitTxDelay
	movwf	TMR0		;5th cycle after write to PORTA

; clear the external interrupt flag, disable the external interrupt,
; and enable the TMR0 interrupt.

	movlw	b'10100000'
	movwf	INTCON

; set the BitCount for the eight bits to send:

	movlw	8
	movwf	BitCount

	return

;------end SendChar--------------------------------------------------

;------begin GetAChar------------------------------------------------

GetAChar
	call	Idle
	btfss	SerialReg,0	;wait for a character to be received
	goto	GetAChar
	bcf	SerialReg,0
	return

;------end GetAChar--------------------------------------------------

;------begin SendAChar-----------------------------------------------

SendAChar
	call	SendChar

WaitToFinish
	call	Idle
	btfsc	SerialReg,1	;wait for the character to be sent
	goto	WaitToFinish
	return

;------end SendAChar-------------------------------------------------

;------begin Idle----------------------------------------------------
;
; Idle should be called whenever the chip is waiting for something
; to happen (waiting for a character to be sent or received, for
; example). Currently, Idle doesn't do anything.

Idle
	return

;------end Idle------------------------------------------------------

;------begin WaitMS--------------------------------------------------
;
; WaitMS is an approximate millisecond delay.

WaitMS
	movlw	_MSDelay
	movwf	MSDelay

RepeatWaitMS
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	decfsz	MSDelay,F
	goto	RepeatWaitMS
	return

;------end WaitMS----------------------------------------------------

;------begin SamplePaddles-------------------------------------------
;
; SamplePaddles checks the status of the paddles.

SamplePaddles
	btfsc	PORTB,_Dit
	goto	DahTest
	btfsc	KeyerFlags,_DitPressed	;if DitPressed already set,
	goto	DahTest			;don't reset DitInLast
	bsf	KeyerFlags,_DitPressed
	bsf	KeyerFlags,_DitInLast

DahTest
	btfsc	PORTB,_Dah
	goto	DoneSample
	btfsc	KeyerFlags,_DahPressed	;if DahPressed already set,
	goto	DoneSample		;don't reset DitInLast
	bsf	KeyerFlags,_DahPressed
	bcf	KeyerFlags,_DitInLast

DoneSample
	return

;------end SamplePaddles---------------------------------------------

;------begin WaitDit-------------------------------------------------
;
; WaitDit is a delay loop which times dits.

WaitDit
	movf	DitLen,W
	movwf	DitLoopIndex

RepeatWaitDit
	call WaitMS
	decfsz	DitLoopIndex,F
	goto	RepeatWaitDit

; check the paddle states:

	call	SamplePaddles

	return

;------end WaitDit---------------------------------------------------

;------begin WaitDah-------------------------------------------------
;
; WaitDah is a delay loop which times dahs. Dahs are three times the
; length of dits.

WaitDah
	call WaitDit
	call WaitDit
	call WaitDit
	return

;------end WaitDah---------------------------------------------------

;------begin WaitWordDit---------------------------------------------
;
; WaitWordDit is a delay loop which times dits based on intercharacter
; spacing.

WaitWordDit
	movf	DitWordLen,W
	movwf	DitLoopIndex

RepeatWaitWordDit
	call WaitMS
	decfsz	DitLoopIndex,F
	goto	RepeatWaitWordDit
	return

;------end WaitWordDit-----------------------------------------------

;------begin WaitWordDah---------------------------------------------
;
; WaitWordDah is a delay loop which times dahs based on intercharacter
; spacing. WordDahs are three times the length of Worddits.

WaitWordDah
	call WaitWordDit
	call WaitWordDit
	call WaitWordDit
	return

;------end WaitWordDah-----------------------------------------------

;------begin FillBuffer----------------------------------------------

FillBuffer

	movlw	0x00		;clear the buffer character count
	movwf	BufCount

	movlw	CharBuf
	movwf	FSR

FillBufferLoop
	call	GetAChar	;wait for a character

	movf	RXBuff,W	;move the rx char into W
	sublw	0x0D		;is this a CR (end of characters) command?
	btfsc	STATUS,Z
	goto	SendString	;if yes, go to send the string.

	movf	BufCount,W	;check to see if the buffer's full. if so,
	sublw	_MaxChars	;don't do anything.
	btfsc	STATUS,Z
	goto	FillBufferLoop

	movf	RXBuff,W	;else, add it to the buffer. move the rx char into W
	movwf	INDF		;store it in the character buffer using indirect
				;addressing

	incf	BufCount,F	;increment the counters
	incf	FSR,F
	goto	FillBufferLoop

;------end FillBuffer------------------------------------------------

;------begin SendCWDit-----------------------------------------------
;
; actually sends the dit over the radio

SendCWDit
	bsf	PORTA,_CW	; turn the bit line on
	bcf	PORTA,_INVCW	; turn on LED
	call	WaitDit		; wait the appropriate amount of time
	bcf	PORTA,_CW	; turn the bit line off
	bsf	PORTA,_INVCW	; turn off LED
	call	WaitDit		; wait for space between dits and dahs
	bcf	KeyerFlags,_DitPressed	;clear the paddle flag
	bsf	KeyerFlags,_DitOutLast	;set "last out was dit" flag
	return

;------end SendCWDit-------------------------------------------------

;------begin SendCWDah-----------------------------------------------
;
; actually sends the dah over the radio

SendCWDah
	bsf	PORTA,_CW	; turn the bit line on
	bcf	PORTA,_INVCW	; turn on LED
	call	WaitDah		; wait the appropriate amount of time
	bcf	PORTA,_CW	; turn the bit line off
	bsf	PORTA,_INVCW	; turn off LED
	call	WaitDit		; wait for space between dits and dahs
	bcf	KeyerFlags,_DahPressed	;clear the paddle flag
	bcf	KeyerFlags,_DitOutLast	;clear "last out was dah" flag
	return

;------end SendCWDah-------------------------------------------------

;------begin SendString----------------------------------------------

SendString			;send the characters in the buffer
	movlw	CharBuf
	movwf	FSR

	movf	BufCount,F	;do this to check for zero in BufCount
	btfsc	STATUS,Z	;if zero, we're done sending. Go to DoneSending.
	goto	DoneSending

SendStringLoop

; check to see if another serial character has been received. If so, stop sending
; and reset everything. Also stop if the user depresses the paddle.

	btfsc	SerialReg,0	;see if a character has been received
	goto	DoneSending	;if so, quit sending.

	btfss	PORTB,_Dit	;dit paddle depressed?
	goto	DoneSending	;if so, quit sending.

	btfss	PORTB,_Dah	;dah paddle depressed?
	goto	DoneSending	;if so, quit sending.

	call	SendCWChar	;else, send the character.
	incf	FSR,F		;set for the next character.
	decf	BufCount,F	;reduce the number of characters remaining by one
	btfsc	STATUS,Z	;if zero, we're done sending. Go to DoneSending.
	goto	DoneSending
	goto	SendStringLoop

DoneSending
	movlw	0x00		;clear the buffer character count
	movwf	BufCount
	movlw	CharBuf		;reset the character buffer pointer
	movwf	FSR
	movlw	'r'		;send the acknowledgement.
	movwf	TXChar
	call	SendAChar
	goto	MainLoop

;------end SendString------------------------------------------------

;------begin SendCWChar----------------------------------------------

; get the character to send:

SendCWChar
	movlw	0x20		;we'll subtract 32 from the ASCII value
				;since we're only interested in characters
				;starting with ASCII 32 (space)
	subwf	INDF,F		;subtract 32 from the next character ASCII value
	btfss	STATUS,C	;if carry is set, the result is zero or positive
	return			;else, return & get next character (because the 
				;ASCII value of the character was less than 32 and
				;therefore of no interest)
	movlw	0x5B
	subwf	INDF,W		;now check to see if the value is too high (over 122,
				;except we already subtracted 32, so check for over 90)
	btfsc	STATUS,C	;if clear, result is negative (ASCII value is too high)
	return			;else, it's okay.

	movlw	HIGH Table	;preset the PCLATH for the computed goto
	movwf	PCLATH

	movf	INDF,W		;now put the value in W for table lookup
	call	Table		;return value will be the bit string to send
	movwf	CharToSend	;store the bit string in CharToSend

; is this a special character? Special characters have MSB on.

	btfsc	CharToSend,7
	goto	SendSpecialChar

; no. find the Start bit. Start looking at bit 6 and continue down until a 1 is encountered.
; the rest of the bits to send will follow.

	rlf	CharToSend,F	; get rid of bit 7.
	movlw	0x08		; load the bit counter so we know when we're out of bits.
	movwf	CharBitCount
	bcf	STATUS,C

FindCWStartBit
	decf	CharBitCount,F	; count the bits we check.
	rlf	CharToSend,F	; look at the next bit. is it a 1? if so, that's our start bit.
	btfss	STATUS,C
	goto	FindCWStartBit

SendCWBit
	decfsz	CharBitCount,F	; check to see if we're out of bits to send.
	goto	SendNextCWBit
	goto	DoneSendingCWBits

SendNextCWBit
	rlf	CharToSend,F	; get the next bit. 1 is a dah, 0 is a dit.
	btfss	STATUS,C
	goto	SendADit
	goto	SendADah

SendADit
	call	SendCWDit	; send the dit
	goto	SendCWBit	; do the next dit or dah

SendADah
	call	SendCWDah	; send the dah
	goto	SendCWBit	; do the next dit or dah

SendSpecialChar
	movlw	b'10000000'	; check to see if it's a space
	subwf	CharToSend,W
	btfss	STATUS,Z	; is the subtraction result 0?
	goto	CheckForIgnore  ; no--see if it's a character we ignore
	call	WaitWordDah	; yes. Wait two dahs and a dit, then do the
	call	WaitWordDah	; next character. Use intercharacter word
	call	WaitWordDit	; spacing. (NOTE: two dahs and a dit chosen
	return			; purely because it sounded good.)

CheckForIgnore
	movlw	0xFF		; ignore character is 255
	subwf	CharToSend,W
	btfss	STATUS,Z	; is the subtraction 0?
	goto	CheckNextSpecialChar
	return

CheckNextSpecialChar		; there aren't any more, so just return.
	return

DoneSendingCWBits

; wait a dah length for the next character.

	call	WaitWordDah	; use the intercharacter spacing

	return

;------end SendCWChar------------------------------------------------

;------begin SetCWSpeed----------------------------------------------

SetCWSpeed

;we're expecting two more bytes: the first one is for the dit length
;and the second one is the interword spacing dit length (to allow
;for Farnsworth-style spacing).

	call	GetAChar	;get the first byte.
	movf	RXBuff,W	;store it.
	movwf	DitLen

	call	GetAChar	;get the interword spacing byte.
	movf	RXBuff,W
	movwf	DitWordLen	;store it.

; send the acknowledgement:

	movlw	'r'
	movwf	TXChar
	call	SendAChar
	goto	MainLoop

;------end SetCWSpeed------------------------------------------------

;------begin SendVersion---------------------------------------------

; send a string with the version in it. The string comes from EEPROM
; memory and is null-terminated. The null terminator is not sent. The
; protocol dictates that the string sent is terminated by a CR, which
; is sent.

SendVersion
	bcf	STATUS,RP0
	clrf	EEADR		;the string we want starts at the 
				;beginning of EEPROM memory.

GetNextVersionChar
	bsf	STATUS,RP0
	bsf	EECON1,RD
	bcf	STATUS,RP0
	movf	EEDATA,W
	btfsc	STATUS,Z	;if the character in W is null, don't
                                ;send any more.
	goto	MainLoop

	movwf	TXChar
	call	SendAChar
	incf	EEADR,F
	goto	GetNextVersionChar

;------end SendVersion-----------------------------------------------


;------begin Paddle--------------------------------------------------

; This section of code handles the keyer for the paddle. The iambic
; keying logic was adapted from the K8 keyer source code by K1EL.

Paddle
	call	SamplePaddles	;get paddle states first time through

PaddleLoop
	btfss	PORTB,_Dit	;are both pressed right now?
	btfsc	PORTB,_Dah
	goto	NotBoth		;no. check for other conditions

	bsf	KeyerFlags,_BothPressed		;both are pressed now. set the flag.
						;we'll need later for mode B

Iambic
	btfss	KeyerFlags,_DitOutLast		;was last a dit?
	goto	DitPaddle			;no--send a dit
	goto	DahPaddle			;yes--send a dah

NotBoth
	btfss	KeyerFlags,_BothPressed		;were both pressed before?
	goto	Single				;no--check for single paddle presses
	bcf	KeyerFlags,_BothPressed		;yes, but not now--clear the flag
	btfsc	PORTB,_Mode			;are we in mode B?
	goto	Iambic				;yes--send the trailing dit/dah
	goto	PaddleDone			;no--finish up.

; were both paddles pressed during last dit/dah (not necessarily at the same time)?
Single
	btfsc	KeyerFlags,_DitPressed		;dit pressed?
	btfss	KeyerFlags,_DahPressed		;and dah?
	goto	SinglePress			;nope. Only one. Find out which one.
	btfsc	KeyerFlags,_DitInLast		;if dit paddle was pressed last, dah
	goto	DahPaddle			;was pressed first, so send dah.
	goto	DitPaddle			;else, send a dit

; only one (or no) paddle was pressed. Find out which and send the dit/dah.

SinglePress
	btfsc	KeyerFlags,_DitPressed		;dit pressed?
	goto	DitPaddle			;yes--send dit
	btfsc	KeyerFlags,_DahPressed		;dah pressed
	goto	DahPaddle			;yes--send dah
	goto	PaddleDone			;nothing pressed. Finish.

DitPaddle
	call	SendCWDit
	goto	PaddleLoop

DahPaddle
	call	SendCWDah
	goto	PaddleLoop

PaddleDone
	clrf	KeyerFlags			;all done. clear the flags and return
	return

;------end Paddle----------------------------------------------------

;------Main Program--------------------------------------------------

Main

; set up the ports as inputs and outputs as needed.

	bsf	STATUS,RP0	;switch to bank 1
	movlw	0xFF
	movwf	TRISB		;make Port B input
	movlw	0x00
	movwf	TRISA		;make Port A output
	bcf	STATUS,RP0

	clrf	PORTA

	bcf	PORTA,_CW	; set CW line low
	bsf	PORTA,_INVCW	; turn off LED

	call	SerSetup	;set up serial comm routines & int.

; set default for the CW speed:

	movlw	0x40
	movwf	DitLen
	movlw	0x40
	movwf	DitWordLen

; clear the flags for manual keyer:

	clrf	KeyerFlags

;------begin MainLoop------------------------------------------------

; this main program simply waits for characters to be received, then
; calls the handler for the command indicated by the received character. It
; also checks the paddle inputs and sends dits/dahs if the paddles are
; depressed.


MainLoop
	call	Paddle		;handle any manual keying

	btfss	SerialReg,0	;has character been received?
	goto	MainLoop	;no, loop again

	bcf	SerialReg,0	;got a char, so clear the flag

	movf	RXBuff,W	;move the rx char into W
	sublw	'<'		;is this the "fill buffer" command?
	btfsc	STATUS,Z
	goto	FillBuffer	;if yes, go to the fill buffer loop

	movf	RXBuff,W	;move the rx char into W
	sublw	'>'		;is this the "set speed" command?
	btfsc	STATUS,Z
	goto	SetCWSpeed	;if yes, go to the set CW speed code

	movf	RXBuff,W	;move the rx char into W
	sublw	'^'		;is this the "version" command?
	btfsc	STATUS,Z
	goto	SendVersion	;if yes, go to SendVersion

	goto	MainLoop

;------end MainLoop--------------------------------------------------

;------begin Table---------------------------------------------------

	org 0x380		;begin the table on a page boundary
Table
	addwf	PCL,F		;compute the GOTO
	retlw	_Char_Space
	retlw	_Char_Ignore
	retlw	_Char_Ignore
	retlw	_Char_Ignore
	retlw	_Char_Ignore
	retlw	_Char_Ignore
	retlw	_Char_Ignore
	retlw	_Char_Ignore
	retlw	_Char_Ignore
	retlw	_Char_Ignore
	retlw	_Char_Asterisk
	retlw	_Char_Plus
	retlw	_Char_Comma
	retlw	_Char_AS
	retlw	_Char_Period
	retlw	_Char_Slash
	retlw	_Char_0
	retlw	_Char_1
	retlw	_Char_2
	retlw	_Char_3
	retlw	_Char_4
	retlw	_Char_5
	retlw	_Char_6
	retlw	_Char_7
	retlw	_Char_8
	retlw	_Char_9
	retlw	_Char_KN
	retlw	_Char_Ignore
	retlw	_Char_Ignore
	retlw	_Char_Equals
	retlw	_Char_Ignore
	retlw	_Char_Question
	retlw	_Char_Ignore
	retlw	_Char_A
	retlw	_Char_B
	retlw	_Char_C
	retlw	_Char_D
	retlw	_Char_E
	retlw	_Char_F
	retlw	_Char_G
	retlw	_Char_H
	retlw	_Char_I
	retlw	_Char_J
	retlw	_Char_K
	retlw	_Char_L
	retlw	_Char_M
	retlw	_Char_N
	retlw	_Char_O
	retlw	_Char_P
	retlw	_Char_Q
	retlw	_Char_R
	retlw	_Char_S
	retlw	_Char_T
	retlw	_Char_U
	retlw	_Char_V
	retlw	_Char_W
	retlw	_Char_X
	retlw	_Char_Y
	retlw	_Char_Z
	retlw	_Char_Ignore
	retlw	_Char_Ignore
	retlw	_Char_Ignore
	retlw	_Char_Ignore
	retlw	_Char_Ignore
	retlw	_Char_Ignore
	retlw	_Char_A
	retlw	_Char_B
	retlw	_Char_C
	retlw	_Char_D
	retlw	_Char_E
	retlw	_Char_F
	retlw	_Char_G
	retlw	_Char_H
	retlw	_Char_I
	retlw	_Char_J
	retlw	_Char_K
	retlw	_Char_L
	retlw	_Char_M
	retlw	_Char_N
	retlw	_Char_O
	retlw	_Char_P
	retlw	_Char_Q
	retlw	_Char_R
	retlw	_Char_S
	retlw	_Char_T
	retlw	_Char_U
	retlw	_Char_V
	retlw	_Char_W
	retlw	_Char_X
	retlw	_Char_Y
	retlw	_Char_Z
	
;------end Table-----------------------------------------------------

;------Version EEPROM------------------------------------------------

	org	0x2100
	de	"Serial CW v1.19 (c) 2002 by NK0E",0x0D,0x00	; Version 1.19
	end


