; ********************************************************************************
;       Filename:		picmrsrc.asm
;       Date:			Tuesday, November 12th, 2008 23:25
;       File Version:   
;       Author:			Lawrence Foltzer
;       Modified by:    Mike Addlesee
;						More comments added and removed some redundant instructions.
;						Minor changes required to port code to the 16F87 from the 16F84.
;						Memory was relocated and some register bank swapping added.
;						Removed use of obsolete instructions.
;						Added debug LEDs to RA0-3.
;						Fixed problem with bank switching in interrupt.
;						Improved cursor management.
;						Increased the tick period to ~6ms.
;						Prompt output of last char of a word.
;						Corrections to dit and dah tables.
;						Removed or commented out some debug code.
;       Size:			372 bytes
; ********************************************************************************
		list    p=16F87 		; list directive to define processor
		#include		<p16F87.inc>		; processor specific variable definitions

;       __CONFIG		_CP_OFF & _WDT_OFF & _PWRTE_ON & _HS_OSC
; timing based on 1.12MHz RC oscillator configuration using 5.1Kohm + 100pf

;       __CONFIG		_CP_OFF & _WDT_OFF & _PWRTE_ON & _RC_OSC
; timing based on 1.12MHz RC oscillator configuration using 5.1Kohm + 100pf

;Program Configuration Register 1
		__CONFIG    _CONFIG1, _CP_OFF & _CCP1_RB3 & _DEBUG_OFF & _WRT_PROTECT_OFF & _CPD_OFF & _LVP_OFF & _BODEN_OFF & _MCLR_OFF & _PWRTE_ON & _WDT_OFF & _EXTRC_CLKOUT

;Program Configuration Register 2
		__CONFIG    _CONFIG2, _IESO_OFF & _FCMEN_OFF

;#define	diagnostics	1	; Set LCD diagnostics "on".

; ********************************************************************************
; Application specific equates.
E       		equ     .4				; LCD strobe control line. Executes a read or write.
LCDR_W  		equ     .5				; LCD read/not-write control line.
RS      		equ     .6				; LCD register select control line.
code_in 		equ     .7      		; PORTB,7 (also ICSPDATA and tone LED).
busy    		equ     .3				; The most significant bit of the LCD's data bus, D7.
FuncSet41       equ     0x2A    		; Command to get the LCD controller's attention.
										; Sets: 4bit LCD bus interface, 2 line display & 5x7 dot characters.
FuncSet42       equ     0x28    		; Command to get the LCD controller into right mode.
										; Sets: 4bit LCD bus interface, 2 line display & 5x7 dot characters.
DisplayOn       equ     0x0c			; Command to get the LCD controller to switch on the display.
										; Sets: Display on & Cursor off.
EntryMode       equ     0x06			; Command to get the LCD controller to set the entry mode.
										; Sets: Increment, but the display is not shifted.
InitDdRam       equ     0x2F			; Command to get the LCD controller to initialise the displays RAM.
										; Sets:  4bit LCD bus interface, , 2 line display & 5x10 dot characters.
lcdLine1Cmd		equ		.128 + .0		; The starting position on the LCD for decoded output on line one.
lcdLine2Cmd		equ		.128 + 0x40		; The starting position on the LCD for decoded output on line two.
Diag1			equ		.128 + 0x40		; The starting position on the LCD for diagnostic output.
Diag2			equ		.128 + 0x44		; The starting position on the LCD for diagnostic output.
Diag3			equ		.128 + 0x48		; The starting position on the LCD for diagnostic output.
Diag4			equ		.128 + 0x4C		; The starting position on the LCD for diagnostic output.
Diag5			equ		.128 + 0x50		; The starting position on the LCD for diagnostic output.
Ddra4Input      equ     B'00000000'		; Program port bits RA0 to RA3 as output pins & RA4 as output pin.
led     		equ     .4      		; RA4 runs LED that indicates presence of a tone.
dled3     		equ     .3      		; RA3 runs debug LED3.
dled2     		equ     .2      		; RA2 runs debug LED2.
dled1     		equ     .1      		; RA1 runs debug LED1.
dled0     		equ     .0      		; RA0 runs debug LED0.
Ddrb4Input      equ     B'10001111'		; Program port bits RB0 to RB3 & RB7 as input pins & RB4 to RB6 as output pins.
Ddrb4Output     equ     B'10000000'
ReadCntrl       equ     B'10100000'		; Set LCD control lines E=0, R/W=1, RS=0. i.e. LCD Control register read.
paddit    		equ     0x0d    		;
paddah    		equ     0x26    		; A largish value that when multiplied by tabsize still fits in a byte.
code_dit_or_dah	equ		0x01			; The codeword representing first dit or dah received.
noise_threshold	equ     0x02    		; 16 milliseconds or less is noise.
tabsize 		equ     .4				; The number of dit or dah measurements that will be stored in a table.
										; Make it a power of two to ease the arithmetic.
optest1			equ		.128 + 0x14		; The first LCD display DDRAM position on line one that is off screen.
optest2			equ		.128 + 0x54		; The first LCD display DDRAM position on line two that is off screen.
timer0_reload	equ		0x09    		; Reload value is 0x09 = 9 Decimal i.e. 256 - 247 for ~6ms tick.
symbol_limit	equ		.6				; Variable symbol_count is considered good if in the range 0 <= symbol_count < symbol_limit.

; The "flags" bit assignments follow:
dit_leads  		equ     .7				; Set (1) for leading DIT of a character, clear (0) for leading DAH of a character.
overflow		equ     .6				; Slow Morse code can cause the counter to overflow.
lcdpresent		equ		.5				; Set if the LCD is present and enabled.
on_lcd_line2	equ		.4				; Clear if on line one of the LCD or set if on line two.
DIT_start		equ		B'10000000'
Timer_overflow	equ		B'01000000'
LCD_on			equ		B'00100000'
line_flag		equ		B'00010000'
										; Indicate initialisation state of all flags
flags_init		equ		0 | LCD_on		; Switch on other bits like this: | DIT_start | Timer_overflow

; ********************************************************************************
; Reserve memory (RAM) for various variables used in the program.
				org     0x20

temp_w  		res     1		; Temporary store for W during the interrupt service routine.
temp_w1  		res     1		; Temporary store for W during the debug print diagnostics.
temp_status     res     1		; Temporary store for STATUS during the interrupt service routine.

flags   		res     1		; In here we will keep a record of certain flags.

PortaImage      res     1		; A shadow copy of the state of port A.
PortbImage      res     1		; A shadow copy of the state of port B.
ACLowNibble		res		1		; Holds low nibble of Address Counter when LCD control register is read.

timecnt 		res     1       ; This is incremented in the ISR,
period  		res     1       ; and transferred here on edge detection.
thres   		res     1       ; The computed symbol type decision threshold.

cursorPos		res		1		; Cursor position on the LCD at which the next decoded character should be printed.
nextDecodePos	res		1		; Saves the next decoded character cursor position during diagnostic output.
lowNibble		res		1		; Temporary store for the low nibble info when displaying a byte in hex format.
offScreen		res		1		; 
scratch1		res		1		; 
scratch2		res		1		; 

codeword		res     1       ; A 1/0 representation of a Morse code character.
symbol_count	res		1		; Counts how many symbols have been received so far for the current character.

; Do not split up these next 6 + tabsize + tabsize registers otherwise their initialisation will fail.
ditptr  		res     1       ; Pointer to ditvals buffer.
ditsum  		res     1
ditave  		res     1
ditvals 		res     tabsize

dahptr  		res     1       ; Pointer to dahvals buffer.
dahsum  		res     1
dahave  		res     1
dahvals 		res     tabsize

; ********************************************************************************
		org     0x000
boot    goto    init			; Do the intialisation code first.

; ********************************************************************************
; This interrupt service routine (ISR) is entered approximately every 6 milliseconds when an IRQ
; is received.
		org     0x004
ISR     movwf   temp_w			; Save the current value in the working register.
		movf    STATUS,W		; Copy the status bits to W.
		movwf   temp_status		; Save the current status bits.
		bcf     STATUS,RP0		; Select register bank 0.
		bcf     PORTA,dled0		; Low output turns the debug LED0 on.
		incf    timecnt,F		; Increment the timer count.
;		btfss   STATUS,Z		; See if the timer count has just overflowed from 0xFF to 0x00.
		btfss   timecnt,.6		; See if the timer count has just overflowed from 0x3F to 0x40.
		goto    isrjmp			; We do this jump if the Z bit is clear (incf result was non-zero).
		bsf     flags,overflow	; Indicate that a timer overflow occurred.
;		decf    timecnt,F		; Decrement the timer count leaving it at 0xFF. I.e. saturate at 0xFF.
		decf    timecnt,F		; Decrement the timer count leaving it at 0x3F. I.e. saturate at 0x3F.
isrjmp  movlw   timer0_reload	; Get the timer 0 reload value.
		movwf   TMR0			; Place the reload value in TMR0.
		bcf     INTCON,TMR0IF	; Clear down the interrupt generated by TMR0.
exit1   movf    temp_status,W	; Get the status bits we saved.
		bsf     PORTA,dled0		; High output turns the debug LED0 off.
		movwf   STATUS			; Restore them to the status register.
		swapf   temp_w,F		; temp_w(0:3) -> temp_w(4:7) -> File register.
		swapf   temp_w,W		; temp_w(0:3) -> temp_w(4:7) -> W register i.e. W restored.
		retfie					; Return from the ISR.

; ********************************************************************************
; Morse tables (see Rec. ITU-R M.1677) follow ISR to keep them in 1st page, hopefully.
; Each look up table consists of 64 (2^6) entries and codeword selects the entry to use.
; One table is for characters having a leading dit
; and the other table for characters with a leading dah.
; The codeword is constructed by rotation left.
; A one is always shifted into the codeword first to represent the leading dit or dah symbol.
; Subsequent symbols are shifted in at the least significant bit (LSB) position for each
; received dit/dah of a character.
; Leading dit => dit = 1 & dat = 0 in the codeword.
; Leading dah => dit = 0 & dah = 1 in the codeword.
; E.g. "a" => .- => 10, so codeword = 00000010 the third "retlw" list entry of the first table.
; E.g. "n" => -. => 10, so codeword = 00000010 the third "retlw" list entry of the second table.
ditab   clrf    PCLATH			; Clear PCLATH.
		movf    codeword,W		; Load W with the codeword.
		andlw   B'00111111'		; We shall only use the six least significant bits.
		addwf   PCL,1			; PCL += W, point program counter (low byte) at the correct entry.
		retlw   " "				; 00000000 => 0x20 - "DIT" Table 1st entry.
		retlw   "e"				; 00000001 => 0x65
		retlw   "a"				; 00000010 => 0x61
		retlw   "i"				; 00000011 => 0x69
		retlw   "w"				; 00000100 => 0x77
		retlw   "r"				; 00000101 => 0x72
		retlw   "u"				; 00000110 => 0x75
		retlw   "s"				; 00000111 => 0x73
		retlw   "j"				; 00001000 => 0x6A
		retlw   "p"				; 00001001 => 0x70
		retlw   0x5f			; 00001010 =>
		retlw   "l"				; 00001011 => 0x6C
		retlw   0x5f			; 00001100 =>
		retlw   "f"				; 00001101 => 0x66
		retlw   "v"				; 00001110 => 0x76
		retlw   "h"				; 00001111 => 0x68
		retlw   "1"				; 00010000 => 0x31
		retlw   0x5f			; 00010001 =>
		retlw   0x5f			; 00010010 =>
		retlw   0x5f			; 00010011 =>
		retlw   0x5f			; 00010100 =>
		retlw   0x2b    		; 00010101 => "+" end of message
		retlw   0x5f			; 00010110 =>
		retlw   0x5f    		; 00010111 => "_" wait
		retlw   "2"				; 00011000 => 0x32
		retlw   0x5f			; 00011001 =>
		retlw   0x5f			; 00011010 =>
		retlw   0x5f			; 00011011 =>
		retlw   "3"				; 00011100 => 0x33
		retlw   "!"     		; 00011101 => 0x21 acknowledge or understood
		retlw   "4"				; 00011110 => 0x34
		retlw   "5"				; 00011111 => 0x35
		retlw   0x5f			; 00100000 =>
		retlw   "'"				; 00100001 => 0x27
		retlw   0x5f			; 00100010 =>
		retlw   0x5f			; 00100011 =>
		retlw   0x5f			; 00100100 =>
		retlw   "@"				; 00100101 => 0x40 commercial at (also known as "arobase")
		retlw   0x5f			; 00100110 =>
		retlw   0x5f			; 00100111 =>
		retlw   0x5f			; 00101000 =>
		retlw   0x5f			; 00101001 =>
		retlw   "."				; 00101010 => 0x2E
		retlw   0x5f			; 00101011 =>
		retlw   0x5f			; 00101100 =>
		retlw   0x22    		; 00101101 => quotation mark
		retlw   0x5f			; 00101110 =>
		retlw   0x5f			; 00101111 =>
		retlw   0x5f			; 00110000 =>
		retlw   0x5f			; 00110001 =>
		retlw   "_"				; 00110010 => 0x5F
		retlw   "?"				; 00110011 => 0x3F (note of interrogation or request for repetition of a transmission not understood)
		retlw   0x5f			; 00110100 =>
		retlw   0x5f			; 00110101 =>
		retlw   0x5f			; 00110110 =>
		retlw   0x5f			; 00110111 =>
		retlw   0x5f			; 00111000 =>
		retlw   0x5f			; 00111001 =>
		retlw   "<"     		; 00111010 => 0x3C end of work
		retlw   0x5f			; 00111011 =>
		retlw   0x5f			; 00111100 =>
		retlw   0x5f			; 00111101 =>
		retlw   0x5f			; 00111110 =>
		retlw   0x5f			; 00111111 => assume an error

dahtab  clrf    PCLATH			; Clear PCLATH.
		movf    codeword,W		; Load W with the codeword.
		andlw   B'00111111'		; We shall only use the six least significant bits.
		addwf   PCL,1			; PCL += W, point program counter at the correct entry.
		retlw   " "				; 00000000 => 0x20 - "DAH" Table 1st entry.
		retlw   "t"				; 00000001 => 0x74
		retlw   "n"				; 00000010 => 0x6E
		retlw   "m"				; 00000011 => 0x6D
		retlw   "d"				; 00000100 => 0x64
		retlw   "k"				; 00000101 => 0x6B invitation to transmit
		retlw   "g"				; 00000110 => 0x67
		retlw   "o"				; 00000111 => 0x6F
		retlw   "b"				; 00001000 => 0x62
		retlw   "x"				; 00001001 => 0x78 "x" or multiplication sign
		retlw   "c"				; 00001010 => 0x63
		retlw   "y"				; 00001011 => 0x79
		retlw   "z"				; 00001100 => 0x7A
		retlw   "q"				; 00001101 => 0x71
		retlw   0x5f			; 00001110 =>
		retlw   0x5f			; 00001111 =>
		retlw   "6"				; 00010000 => 0x36
		retlw   "="     		; 00010001 => 0x3D double dash
		retlw   "/"     		; 00010010 => 0x2F fraction bar
		retlw   0x5f			; 00010011 =>
		retlw   0x5f			; 00010100 =>
		retlw   ">"     		; 00010101 => 0x3E starting signal (to precede every transmission)
		retlw   "("				; 00010110 => 0x28
		retlw   0x5f			; 00010111 =>
		retlw   "7"				; 00011000 => 0x37
		retlw   0x5f			; 00011001 =>
		retlw   0x5f			; 00011010 =>
		retlw   0x5f			; 00011011 =>
		retlw   "8"				; 00011100 => 0x38
		retlw   0x5f			; 00011101 =>
		retlw   "9"				; 00011110 => 0x39
		retlw   "0"				; 00011111 => 0x30
		retlw   0x5f			; 00100000 =>
		retlw   "-"				; 00100001 => 0x2D
		retlw   0x5f			; 00100010 =>
		retlw   0x5f			; 00100011 =>
		retlw   0x5f			; 00100100 =>
		retlw   0x5f			; 00100101 =>
		retlw   0x5f			; 00100110 =>
		retlw   0x5f			; 00100111 =>
		retlw   0x5f			; 00101000 =>
		retlw   0x5f			; 00101001 =>
		retlw   ";"				; 00101010 => 0x3B
		retlw   "!"				; 00101011 => 0x21
		retlw   0x5f			; 00101100 =>
		retlw   ")"				; 00101101 => 0x29
		retlw   0x5f			; 00101110 =>
		retlw   0x5f			; 00101111 =>
		retlw   0x5f			; 00110000 =>
		retlw   0x5f			; 00110001 =>
		retlw   0x5f			; 00110010 =>
		retlw   ","				; 00110011 => 0x2C
		retlw   0x5f			; 00110100 =>
		retlw   0x5f			; 00110101 =>
		retlw   0x5f			; 00110110 =>
		retlw   0x5f			; 00110111 =>
		retlw   ":"				; 00111000 => 0x3A
		retlw   0x5f			; 00111001 =>
		retlw   0x5f			; 00111010 =>
		retlw   0x5f			; 00111011 =>
		retlw   0x5f			; 00111100 =>
		retlw   0x5f			; 00111101 =>
		retlw   0x5f			; 00111110 =>
		retlw   0x5f			; 00111111 =>

; ********************************************************************************
; Initialisation code to get the LCD configured.
lcd_init
		btfss	flags,lcdpresent
		return
		call    ChkBusy			; Prepare to write to the LCD using only the upper nibble of the LCD's data bus.

		movlw   FuncSet41		; Get the command we want to write to the LCD.
		call    SendCmmd		; Strobe command to the LCD.
		movlw   FuncSet42		; Get the command we want to write to the LCD.
		call    SendCmmd		; Strobe command to the LCD.
		movlw   DisplayOn		; Get the command we want to write to the LCD.
		call    SendCmmd		; Strobe command to the LCD.
		movlw   EntryMode		; Get the command we want to write to the LCD.
		call    SendCmmd		; Strobe command to the LCD.
		movlw   InitDdRam		; Get the command we want to write to the LCD.
		call    SendCmmd		; Strobe command to the LCD.
		movlw	lcdLine1Cmd		; Load the command that will take us to the left most char on LCD line one.
		movwf	cursorPos		; Initialise the starting position for writing stuff to the LCD.
		call    SendCmmd		; Strobe command to the LCD.
		return

; ********************************************************************************
; Initialisation code.
; Do clock dependant stuff.
; Next two opcodes used if xtal clock selected.
;init   movlw   0x04			; Divide XTAL OSC by 4 and then 32 for 4ms ticks.
;       option					; Later, load TMR0 with 256-140 and let overflow.
; Next five opcodes used if RC clock selected.
init    bcf     STATUS,RP1		; We select register bank pair 0 or 1.
		movlw   0x02    		; Divide RC OSC by 4 and then 8 for ~6ms ticks.
		bsf     STATUS,RP0		; Select register bank 1.
		movwf	OPTION_REG      ; Later, load TMR0 with timer0_reload and let overflow.
		bcf     STATUS,RP0		; Select register bank 0.

; Setup ports A & B.
		movlw   Ddra4Input		; Program RA0 to RA3 as output pins & RA4 as an output.
		bsf     STATUS,RP0		; Select register bank 1.
		movwf	TRISA			; Program port A data direction register.
		bcf     STATUS,RP0		; Select register bank 0.
		bcf     PORTA,led       ; On initially - low output turns the LED on.
		bsf     PORTA,dled3		; High output turns the debug LED3 off.
		bsf     PORTA,dled2		; High output turns the debug LED2 off.
		bsf     PORTA,dled1		; High output turns the debug LED1 off.
		bsf     PORTA,dled0		; High output turns the debug LED0 off.
		movlw   Ddrb4Input		; Program RB0 to RB3 & RB7 as input pins & RB4 to RB6 as output pins.
		bsf     STATUS,RP0		; Select register bank 1.
		movwf	TRISB			; Program port B data direction register.
		bcf     STATUS,RP0		; Select register bank 0.

; Construct a delay loop by temporarily using the port A & B shadow registers.
		clrf    PortbImage		; Start with port B shadow state all clear.
		clrf    PortaImage		; Start with port A shadow state all clear.
dlp1    decfsz  PortaImage,F	; Count down using the port A shadow.
		goto    dlp1			; Keep counting down until we hit zero, then we skip this instruction.
		decfsz  PortbImage,F	; Count down using the port B shadow. This forms an outer loop to the port A inner loop count.
		goto    dlp1			; Keep counting down until we hit zero, then we skip this instruction.

; Initialise the dit and dah pointers, sums, averages and tables.
		movlw   .13				; 1 + 1 + 1 + tabsize + 1 + 1 + 1 + tabsize.
		movwf   ditptr			; Load the number of bytes to clear.
		movlw   ditptr			; Load W with the address of ditptr.
		movwf   FSR				; Set up indirect addressing so we can use INDF.
ilp1    incf    FSR,F			; Increment the FSR pointer to the next location.
		clrf    INDF			; Clear the register pointed to by FSR
		decfsz  ditptr,1		; Decrease the count of registers to clear.
		goto    ilp1			; Loop back if there are more registers to clear.

; Pad the dit and dah buffers and set the initial averages so we get sensible behaviour quickly.
		movlw	paddit
		movwf	ditvals
		movwf	ditvals + 1
		movwf	ditvals + 2
		movwf	ditvals + 3
		movwf	ditave
		movlw	tabsize * paddit
		movwf	ditsum
		movlw	paddah
		movwf	dahvals
		movwf	dahvals + 1
		movwf	dahvals + 2
		movwf	dahvals + 3
		movwf	dahave
		movlw	tabsize * paddah
		movwf	dahsum

		movlw   (paddit + paddah / 2)
		movwf   thres			; Set the decision threshold.

		movlw   timer0_reload	; Get timer 0 reload value.
		movwf   TMR0			; Place the reload value in the 8-bit real-time clock/counter TMR0.

		movlw	flags_init		; Find out the initialisation state of all the flags.
		movwf   flags			; Set initialisation state of all the flags.

		call	lcd_init		; Initialise the LCD if it is present. Make sure this is done after flags setup.

		clrf    INTCON			; Clear all interrupt enables and interrupt flags.
		bsf     INTCON,TMR0IE	; Enable the TMR0 interrupts.
		bsf     INTCON,GIE		; Enable all unmasked interrupts.
; Fall through to the first tone processing.

; ********************************************************************************
; Process the very first tone received.
await_space
;		call	test_interrupts	; Make sure interrupts are still running.
		btfss   PORTB,code_in	; Sample the tone decoder signal on port B bit 7.
								; The bit will be low (0) when a tone is present i.e. key-down.
		goto    await_space   	; Keep looping back to "await_space" while we are receiving a tone.

; The tone has now disappeared i.e. we've just hit the start of key-up period.
		bsf     PORTA,led		; We are now receiving a space, so switch off the LED.

await_tone
;		call	test_interrupts	; Make sure interrupts are still running.
		btfsc   PORTB,code_in	; Sample the tone decoder signal on port B bit 7.
		goto    await_tone		; Keep looping back to "await_tone" while there is no tone.
; Fall through to start_of_char.

; ********************************************************************************
; Look for the first symbol of a codeword.
; By "symbol" we mean the dits or dahs that make up a character.
; We come back to here for detection and classification of
; the tone pulse that forms the first symbol of a character.
start_of_char
; We have detected the start of a dit or dah, or noise from a station on a nearby frequency (or key bounce)!
		clrf	symbol_count	; Start counting the incoming symbols.
		call	measure_tone

; The tone has stopped, but we believe this is the first
; symbol of a character so we don't know what it is yet.
; We shall assume it is a dah on the very first pass through the code
; as flags was cleared and hence dit_leads = 0.
		movlw   code_dit_or_dah	; The first symbol in a codeword is always represented by a one.
		movwf   codeword		; Write the first symbol into the codeword.
; Look at the last tone/mark pulse measurement.
		movf    thres,W			; Load the dit/dah threshold into the W register.
		subwf   period,W		; Calculate W = (period - thres).
		btfsc   STATUS,C		; The Carry bit (NOT-Borrow) is set if period >= thres.
		goto    isadah			; We think we heard a dah as the tone period >= thres.

; We think we heard a dit as the tone period < thres.
isadit
		bsf     flags,dit_leads	; Set (1) as we believe the next character has a leading dit.
		call    avedit			; Update the dit average given the new dit.
		goto    codeword_loop

; We think we heard a dah as the tone period >= thres.
isadah
		bcf     flags,dit_leads	; Clear (0) as we believe the next character has a leading dah.
		call    avedah			; Update the dah average given the new dah.
; Fall through into the main program loop.

; ********************************************************************************
; Compile the rest of the codeword symbol by symbol.
; We come back to here for each tone pulse detected other than the first symbol of a character.
; Now we wait for a tone to start again so we can see what kind of space just passed.
codeword_loop
;		call	test_interrupts	; Make sure interrupts are still running.
		movf    thres,W			; Load the dit/dah threshold into the W register.
								; The same threshold can be used to distinguish
								; between inter symbol and inter character spaces.
		subwf   timecnt,W		; Calculate W = (timecnt - thres).
		btfsc   STATUS,C		; The Carry bit (NOT-Borrow) is set if timecnt >= thres.
		goto    trim_space
; We have only heard what might be an inter symbol space so far so continue listening for a tone.
		btfsc   PORTB,code_in	; Sample the tone decoder signal on port B bit 7.
		goto    codeword_loop	; Keep looping back to "codeword_loop" while there is no tone.

; A tone has started again.
		incf	symbol_count	; Count this symbol.
		call	measure_tone

; Look at the last tone/mark pulse measurement.
		movf    thres,W			; Load the dit/dah threshold into the W register.
		subwf   period,W		; Calculate W = (period - thres).
		btfsc   STATUS,C		; The Carry bit (NOT-Borrow) is set if period >= thres.
		goto    itsadah			; We think we heard a dah as the tone period >= thres.
;		goto    itsadit			; We think we heard a dit as the tone period < thres.

; ********************************************************************************
; We think we heard a dit as the tone period < thres.
itsadit
		call    avedit			; Update the dit average given the new dit.
		btfsc   flags,dit_leads
		bsf     STATUS,C		; Leading symbol = dit => Use Carry = 1 for dits.
		btfss   flags,dit_leads
		bcf     STATUS,C		; Leading symbol = dah => Use Carry = 0 for dits.
		rlf     codeword,F		; Rotate left the next symbol into the codeword.
		goto    codeword_loop	; Find next tone start.

; ********************************************************************************
; We think we heard a dah as the tone period >= thres.
itsadah
		call    avedah			; Update the dah average given the new dah.
		btfss   flags,dit_leads
		bsf     STATUS,C		; Leading symbol = dah => Use Carry = 1 for dahs.
		btfsc   flags,dit_leads
		bcf     STATUS,C		; Leading symbol = dit => Use Carry = 0 for dahs.
		rlf     codeword,F		; Rotate left the next symbol into the codeword.
		goto    codeword_loop	; Find next tone start.

; ********************************************************************************
; We want to time the period for which a tone is present.
measure_tone
		clrf    timecnt 		; Reset the timer count to start measuring the tone/mark period. It will increment each time the ISR runs.
		bcf     flags,overflow	; Clear timer overflow flag.
		bcf     PORTA,led		; Light the LED to indicate we are hearing a tone.

debounce
;		call	test_interrupts	; Make sure interrupts are still running.
		movlw   noise_threshold	; Debounce the tone edge. Load W with the noise threshold.
		subwf   timecnt,W		; Calculate W = (timecnt - noise_threshold).
		btfss   STATUS,C		; The Carry bit (NOT-Borrow) is set if timecnt >= noise_threshold.
		goto    debounce		; Keep looping back to "debounce" until we've waited longer than the noise threshold.

; We have exceeded the debounce period. Now wait for the end of the tone.
wait_for_space
;		call	test_interrupts	; Make sure interrupts are still running.
		btfss   PORTB,code_in	; Sample the tone decoder signal on port B bit 7.
		goto    wait_for_space	; Keep looping back to wait_for_space while we are receiving a tone.

; The tone has stopped.
		movf    timecnt,W		; Take a snapshot of the timer count. Once in W the ISR can no longer affect our measurement.
		movwf   period			; Record the timer count measurement of the tone/mark period.
;TO DO: Record overflow flag also.

		clrf    timecnt			; Reset the timer count to start measuring the space period.
		bcf     flags,overflow	; Clear timer overflow flag.
		bsf     PORTA,led		; We are now receiving a space, so switch off the LED.
		return

; ********************************************************************************
; Processing for spaces longer than an inter symbol space.
trim_space
		clrf    timecnt			; Reset the timer count to trim the space period by the threshold interval.
		bcf     flags,overflow	; Clear timer overflow flag.
; The space so far is long enough to be either an inter character or an inter word space so now decode and print out the char.

; Check number of symbols received.
		movlw	symbol_limit
		subwf	symbol_count,W	; Calculate W = (symbol_count - symbol_limit).
		btfss   STATUS,C		; The Carry bit (NOT-Borrow) is set if symbol_count >= symbol_limit.
		goto	good_symb_count

; Too many symbols to allow us to use the dit and dah tables so assume an error (eight dits).
		movlw   0x5f    		; Load an ASCII "_" character into W.
		call	SendText		; Print the character in W by sending it to the LCD.
		goto	codeword_loop2

good_symb_count
		btfsc   flags,dit_leads	; Workout what table to use given the dit_leads flag value.
		goto    ditstart
		call    dahtab			; Look up the character and return its ASCII value in W.
		goto    char_of_word
ditstart
		call    ditab			; Look up the character and return its ASCII value in W.
char_of_word
		call	SendText		; Print the character in W by sending it to the LCD.

codeword_loop2
;		call	test_interrupts	; Make sure interrupts are still running.
		movf    thres,W			; Load the dit/dah threshold into the W register.
								; The same threshold can be used to distinguish
								; between inter character and inter word spaces.
		subwf   timecnt,W		; Calculate W = (timecnt - thres).
		btfsc   STATUS,C		; The Carry bit (NOT-Borrow) is set if timecnt >= thres.
		goto    trim_again

; We have only heard what might be an inter character space so far so continue listening for a tone.
		btfsc   PORTB,code_in	; Sample the tone decoder signal on port B bit 7.
		goto    codeword_loop2	; Keep looping back to "codeword_loop2" while there is no tone.

; A tone has started again.
		goto	start_of_char

trim_again
		clrf    timecnt			; Reset the timer count to trim the space period by the threshold interval.
		bcf     flags,overflow	; Clear timer overflow flag.

codeword_loop3
;		call	test_interrupts	; Make sure interrupts are still running.
		movf    thres,W			; Load the dit/dah threshold into the W register.
								; The same threshold can be used to distinguish
								; between inter character and inter word spaces.
		subwf   timecnt,W		; Calculate W = (timecnt - thres).
		btfsc   STATUS,C		; The Carry bit (NOT-Borrow) is set if timecnt >= thres.
; A period roughly equivalent to six dit periods has elapsed without a tone starting so we can print out an interword space.
		goto    print_interword_space

; We have only heard what might be an inter character space so far so continue listening for a tone.
		btfsc   PORTB,code_in	; Sample the tone decoder signal on port B bit 7.
		goto    codeword_loop3	; Keep looping back to "codeword_loop3" while there is no tone.

; A tone has started again.
		goto	start_of_char

print_interword_space
; The space so far is long enough to be an inter word space so now print out a space char.
		movlw   0x20    		; Load an ASCII space character into W.
		call	SendText		; Print the character in W by sending it to the LCD.
		goto	await_tone

; ********************************************************************************
; Update the dit average given the new dit period measurement.
avedit
		movf    ditptr,W		; Load W with the index into the ditvals (dit values) buffer for the oldest measurement.
		addlw   ditvals			; Add the address of the start of the ditvals buffer.
		movwf   FSR				; Set up indirect addressing so we can use INDF.
		movf    INDF,W			; Load W with the value at the indexed position in the ditvals buffer.
		subwf   ditsum,F		; Calculate (new)ditsum = ((old)ditsum - W). Remove the oldest measurement from the sum.
		movf    period,W		; Load the measured period of the latest dit into W.
		movwf   INDF			; Save it to the ditvals buffer overwriting the oldest measurement.
		addwf   ditsum,F		; Calculate (new)ditsum = ((old)ditsum + W). Add the latest measurement to the sum.
		rrf     ditsum,W		; Divide ditsum by two and save result in W.
								; N.B. Carry may not be clear because of the addwf instuction.
		movwf   ditave			; Save the result so far in ditave.
								; It's not the right quantity yet, it's double the average!
		rrf     ditave,F		; Divide ditave by two to generate the correct average value.
		movlw   0x3f			; Load a mask that we shall use to clear the two most significant bits.
		andwf   ditave,F		; Apply the mask to ditave.
								; This counteracts the two possibly non-zero Carry bits rotated in by the rrf instructions.
		incf    ditptr,F		; Increment the index into the ditvals buffer.
		movlw   tabsize - 1		; But we may have gone past the last entry in the buffer, so load a mask value.
		andwf   ditptr,F		; Use the mask value to enforce a wrap back to the beginning of the buffer.
		goto    estimate_threshold
								; Now go update our estimate of the dit/dah threshold.

; ********************************************************************************
; Update the dah average given the new dah period measurement.
avedah
		movf    dahptr,W		; Load W with the index into the dahvals (dah values) buffer for the oldest measurement.
		addlw   dahvals			; Add the address of the start of the dahvals buffer.
		movwf   FSR				; Set up indirect addressing so we can use INDF.
		movf    INDF,W			; Load W with the value at the indexed position in the dahvals buffer.
		subwf   dahsum,F		; Calculate (new)dahsum = ((old)dahsum - W). Remove the oldest measurement from the sum.
		movf    period,W		; Load the measured period of the latest dah into W.
		movwf   INDF			; Save it to the dahvals buffer overwriting the oldest measurement.
		addwf   dahsum,F		; Calculate (new)dahsum = ((old)dahsum + W). Add the latest measurement to the sum.
		rrf     dahsum,W		; Divide dahsum by two and save result in W.
								; N.B. Carry may not be clear because of the addwf instuction.
		movwf   dahave			; Save the result so far in dahave.
								; It's not the right quantity yet, it's double the average!
		rrf     dahave,F		; Divide dahave by two to generate the correct average value.
		movlw   0x3f			; Load a mask that we shall use to clear the two most significant bits.
		andwf   dahave,F		; Apply the mask to dahave.
								; This counteracts the two possibly non-zero Carry bits rotated in by the rrf instructions.
		incf    dahptr,F		; Increment the index into the dahvals buffer.
		movlw   tabsize - 1		; But we may have gone past the last entry in the buffer, so load a mask value.
		andwf   dahptr,F		; Use the mask value to enforce a wrap back to the beginning of the buffer.
; Fall through to estimate_threshold.

; ********************************************************************************
; Update our estimate of the dit/dah threshold.
; We shall estimate the threshold to be the period
; exactly halfway between the dit and dah averages.
estimate_threshold
		bcf     PORTA,dled2		; Low output turns the debug LED2 on.
		bsf     PORTA,dled2		; High output turns the debug LED2 off.
		movf    ditave,W		; Use our averaged dit and dah values.
		subwf   dahave,W		; Calculate W = (dahave - ditave).
		movwf   thres			; Save result to threshold register.
		bcf     STATUS,C		; Clear the Carry bit.
		rrf     thres,F			; Divide by 2.
		movf    ditave,W		; Load W with the smaller of the two averages.
		addwf   thres,F			; Add the divided by 2 value to obtain the new threshold.
; Fall through to lcd_position_test.

; ********************************************************************************
; Monitor LCD writing position to make sure we stay on screen.
; Reconstruct the current LCD writing position from the data in PortbImage and ACLowNibble.
lcd_position_test
;		swapf	ACLowNibble,W	; Swap the L.S.Nibble of the read AC data to its correct position in the L.S.Nibble of W.
;		andlw	0x0f			; Mask out the rubbish in the upper nibble.
;		movwf	ACLowNibble		; Put it back in storage.
;		movf	PortbImage,W	; Load W with the most significant nibble of the read AC data.
;		andlw	0x70			; Mask out the rubbish in the lower nibble and what was the BF (busy flag).
;		iorwf	ACLowNibble,W	; Inclusive OR the two nibbles together, leaving the result in W.
;		movwf	cursorPos		;DEBUG Save the next position for writing decoded characters.
;		incf	cursorPos,F		; Save the next position for writing decoded characters.

#ifndef diagnostics
		movlw	optest1			; Load the test value that determines if the next decoded character would be written off the end of line 1.
		btfsc	flags,on_lcd_line2
		movlw	optest2			; Load the test value that determines if the next decoded character would be written off the end of line 2.
		subwf   cursorPos,W		; Calculate W = (cursorPos - optestX).
		btfss   STATUS,C		; The Carry bit (NOT-Borrow) is set if cursorPos >= optestX.
		return
off_screen
		movlw	lcdLine1Cmd		; Load the command that will take us to the left most char on LCD line one.
		btfss	flags,on_lcd_line2
		movlw	lcdLine2Cmd		; Load the command that will take us to the left most char on LCD line two.
		movwf	cursorPos		; Copy the nextDecodePos cursor position into cursorPos.
		call	SendCmmd		; Command the LCD module to start writing from a new cursor position.
		movlw	line_flag		; Pick out thr flag "on_lcd_line2".
		xorwf	flags,F			; Toggle it.
		return

#else
		movlw	optest1			; Load the test value that determines if the next decoded character would be written off screen.
		subwf   cursorPos,W		; Calculate W = (cursorPos - optest1).
		movf	cursorPos,W		; Reload the cursor position into W.
		btfsc   STATUS,C		; The Carry bit (NOT-Borrow) is set if cursorPos >= optest1.
beyond_row_one
		movlw	lcdLine1Cmd		; Load the command that will take us to the left most char on LCD line one.
		movwf	nextDecodePos	; Save the next position command for writing decoded characters.

; ********************************************************************************
; Use our LCD display routines to present diagnostic info to the user.
print_diagnostics
		movlw   Diag1			; Load W with the required LCD cursor position.
		call	SendCmmd		; Command the LCD module to start writing from a new cursor position.
		movf    period,W		; Load W with the measured period.
		call	print_byte_as_hex; Convert the integer value in W to ASCII and print it to LCD.
		movlw   Diag2			; Load W with the required LCD cursor position.
		call	SendCmmd		; Command the LCD module to start writing from a new cursor position.
		movf    ditave,W		; Load W with the smaller of the two averages.
		call	print_byte_as_hex; Convert the integer value in W to ASCII and print it to LCD.
		movlw   Diag3			; Load W with the required LCD cursor position.
		call	SendCmmd		; Command the LCD module to start writing from a new cursor position.
		movf    dahave,W		; Load W with the larger of the two averages.
		call	print_byte_as_hex; Convert the integer value in W to ASCII and print it to LCD.
		movlw   Diag4			; Load W with the required LCD cursor position.
		call	SendCmmd		; Command the LCD module to start writing from a new cursor position.
		movf    thres,W			; Load W with the threshold value.
		call	print_byte_as_hex; Convert the integer value in W to ASCII and print it to LCD.
		movlw   Diag5			; Load W with the required LCD cursor position.
		call	SendCmmd		; Command the LCD module to start writing from a new cursor position.
		movf    nextDecodePos,W	; Load W with the cursor position to be written to next.
		call	print_byte_as_hex; Convert the integer value in W to ASCII and print it to LCD.

		movf	nextDecodePos,W	; Restore the LCD to the cursor position for writing the next decoded character to.
		movwf	cursorPos		; Copy the nextDecodePos cursor position into cursorPos.
		call	SendCmmd		; Command the LCD module to start writing from a new cursor position.
		return					; Return from the call to avedit or avedah.
#endif	;diagnostics

; ********************************************************************************
; General purpose delay loop.
delay_loop
		clrf	scratch2		; Start with scratch2 all clear.
		clrf	scratch1		; Start with scratch1 all clear.
dlp3	decfsz	scratch1,F		; Count down using scratch1.
		goto	dlp3			; Keep counting down until we hit zero, then we skip this instruction.
		decfsz	scratch2,F		; Count down using scratch2. This forms an outer loop to the scratch1 inner loop count.
		goto	dlp3			; Keep counting down until we hit zero, then we skip this instruction.
		return

; ********************************************************************************
; LCD Controller Information.
; DISPLAY COMMAND TABLE for Epson SED1278F/D Dot Matrix LCD Controller Driver.
;	Parameter				RS	R/W	DB7	DB6	DB5	DB4	DB3	DB2	DB1	DB0	Note
;	CLEAR DISPLAY			0	0	0	0	0	0	0	0	0	1
;	CURSOR HOME				0	0	0	0	0	0	0	0	1	1
;	ENTRY MODE SET			0	0	0	0	0	0	0	1	I/D	I/D	DB1=1: Increment,   DB1=0: Decrement
;																	DB0=1: The display is shifted.
;																	DB0=0: The display is not shifted.
;	DISPLAY ON/OFF			0	0	0	0	0	0	1	D	C	C	DB2=1: Display on,  DB2=0: Display off
;																	DB1=1: Cursor on,   DB1=0: Cursor off
;																	DB0=1: Blinking on, DB0=0: Blinking off
;	CURSOR/DISPLAY SHIFT	0	0	0	0	0	1	S/C	R/L	*	*	DB3=1: Shifts display one character
;																	DB2=1: Right shift, DB2=0: Left shift
;	SYSTEM SET				0	0	0	0	1	DL	N	F	*	*	DB4=1: 8 bits,      DB4=0: 4 bits
;																	DB3=1: 2 line display (1/16 duty),
;																	DB3=0: 1 line display
;																	DB2=1: 5x10 dots, 1/11 duty
;																	DB2=0: 5x7 dots, 1/8 duty
;	SET CGRAM ADDRESS		0	0	0	1	Acg	(DB0 to 5)			The address length that can be set is 64 addresses.
;	SET DDRAM ADDRESS		0	0	1		Add	(DB0 to 6)			The address length that can be set is 80 addresses.
;	READ BUSY FLAG/			0	1	BF		AC	(DB0 to 6)			DB7=1: Busy (instruction not accepted)
;		ADDRESS COUNTER												DB7=0: Ready (instruction accepted)
;	WRITE DATA				1	0			Write Data
;	READ DATA				1	1			Read Data
; * => Don't Care.

; ********************************************************************************
; Subroutines SendText & SendCmmd. A byte of command or data is passed to these
; subroutines in the working register, W, and is written to the command or data
; register of the LCD using two nibble transfers employing only the most significant
; nibble of the LCD's data bus.
SendText
		btfss	flags,lcdpresent
		return
		clrf    PORTB			; Prepare for an LCD Control register write.
		bsf     PORTB,RS		; Switch to the LCD's data register.
		incf	cursorPos,F		; Keep track of where the cursor is.
		goto    Send1
; Strobe the command passed in the working register through to the LCD.
SendCmmd
		btfss	flags,lcdpresent
		return
		clrf    PORTB			; Prepare for an LCD Control register write.
Send1   movwf   PortbImage		; Copy the command or data to the port B shadow register.
		swapf   PortbImage,W	; Swap the two port B shadow nibbles and put result in W.
		andlw   0x0f			; Select the lower nibble of W i.e. the upper nibble of the command/data.
		iorwf   PORTB,1			; Inclusive OR W and port B (for the LCD control bus bits) then send the result back to port B.
		bsf     PORTB,E			; Strobe E set. N.B. This strobe is active high.
		bcf     PORTB,E			; Strobe E cleared completing the write of the most significant nibble of the command/data.
		movlw   0xf0			; Load mask into W for the upper nibble.
		andwf   PORTB,1			; AND W and port B thus clearing the most significant nibble of the LCD's data bus and then send result back to port B.
		movf    PortbImage,W	; Recover from the port B shadow into W the original command/data we were asked to write.
		andlw   0x0f			; Get the least significant nibble of the command/data.
		iorwf   PORTB,1			; Inclusive OR W and port B (for the LCD control bus bits) then send the result back to port B.
		bsf     PORTB,E			; Strobe E set. N.B. This strobe is active high.
		bcf     PORTB,E			; Strobe E cleared completing the write of the least significant nibble of the command/data.
; N.B. Subroutines SendText & SendCmmd fall through into the ChkBusy routine and return from there.

; ********************************************************************************
; Subroutine ChkBusy. Queries the LCD controller until it is in the not busy state.
; Then reconfigures port B in preparation for writing to the LCD.
ChkBusy
		btfss	flags,lcdpresent
		return
		movlw	Ddrb4Input		; Program RB0 to RB3 & RB7 as input pins & RB4 to RB6 as output pins.
		bsf		STATUS,RP0		; Select register bank 1.
		movwf	TRISB			; Program port B data direction register.
		bcf		STATUS,RP0		; Select register bank 0.
SampleAgain
		bcf     PORTA,dled3		; Low output turns the debug LED3 on.
		bsf     PORTA,dled3		; High output turns the debug LED3 off.
		movlw	ReadCntrl		; Get LCD control line values.
		movwf	PORTB			; Start an LCD Control register read.
		bsf		PORTB,E			; Bring the LCD strobe control line, E, back high to read the most significant nibble.
		movf	PORTB,W			; Read the data presented on port B.
		movwf	PortbImage		; Copy it to the port B shadow state.
		bcf		PORTB,E			; Bring the LCD strobe control line, E, low.
		bsf		PORTB,E			; Bring the LCD strobe control line, E, high to read the least significant nibble.
		movf	PORTB,W			; Read the data presented on port B.
		movwf	ACLowNibble		; Copy it to the storage location for the Address Counter least significant nibble.
		bcf		PORTB,E			; Bring the LCD strobe control line, E, low.
		btfsc	PortbImage,busy	; Test the "busy" bit i.e. LCD data bus bit 7.
		goto	SampleAgain		; Go back for another test if the LCD controller is still busy.
		movlw	Ddrb4Output		; LCD controller not busy ("busy" bit = 0), so prepare for port B output.
		bsf		STATUS,RP0		; Select register bank 1.
		movwf	TRISB			; Program port B data direction register for RB0 to RB6 as output pins. RB7 remains an input pin.
		bcf		STATUS,RP0		; Select register bank 0.
		return					; Return knowing LCD is not busy and port B is configured to be written to.

; ********************************************************************************
; Subroutine move_cursor. Moves the LCD cursor to the position indicated in the W register.
move_cursor
								; TO DO
		return

; ********************************************************************************
; Subroutine print_itoa. Turns an unsigned 8-bit integer value passed in the W
; register into an ASCII string that is printed to the current cursor position
; on the LCD. We shall see a number in the range 0 to 255 plus a trailing space.
; Exactly four characters will be written.
print_itoa
								; TO DO
		return

; ********************************************************************************
; print_byte_as_hex displays a byte passed in W as two hexadecimal characters on the LCD.
; Exactly three characters will be written, the third character being a trailing space.
; The ASCII string is printed at the current cursor position on the LCD.
print_byte_as_hex
		btfss	flags,lcdpresent
		return
		movwf	lowNibble		; Save the lower nibble for later.
		swapf	lowNibble,W		; Swap the upper nibble to the lower nibble position.
		call	nibble_to_ASCII	; Convert the lower nibble to ASCII.
		call	SendText		; Send this character to the LCD.
		movf	lowNibble,W		; Recover the lower nibble.
		call	nibble_to_ASCII	; Translate it also.
		call	SendText		; Send this character to the LCD.
		movlw	0x20			; Prepare to send a trailing ASCII space character.
		call	SendText		; Send this character to the LCD.
		return

; ********************************************************************************
; Routine to translate the lower nibble of the W register to an ASCII character in the W register.
nibble_to_ASCII
		bcf     STATUS,C		; Clear the Carry bit.
		andlw	B'00001111'		; Mask out the upper nibble.
		addlw	-.10			; Set carry if W value was in the range 0 to 9.
		btfsc	STATUS,C		; Skip next instruction for numeric characters (carry clear for numerics and set for alpha).
		goto	alpha_value		; W value was in the range 10 to 15.
		addlw	.10 + A'0'		; Add ten to restore range to 0 to 9, then A'0' to produce equivalent ASCII characters.
		return
alpha_value
		addlw	A'A'			; Add A'A' to produce equivalent ASCII characters. I.e. (0..5) + A'A' => (A'A'..A'F').
		return

; ********************************************************************************
; Routine to test that the interrupts are still running.
test_interrupts
		movwf   temp_w1			; Save the current value in the working register.
		bcf		PORTA,dled1		; Low output turns the debug LED1 on.
		btfss	INTCON,TMR0IE	; Skip next instruction if bit is set.
		goto	locked_up		; Timer interrupt got switched off.
		btfss	INTCON,GIE		; Skip next instruction if bit is set.
locked_up
		goto	locked_up		; Global interrupt enable got switched off.
 		bsf		PORTA,dled1		; High output turns the debug LED1 off.
		movf	temp_w1,W		; Restore the working register.
		return

;**********************************************************************
; This version is based on use of an NE567 tone decoder as input filter.
; Decoder output is low when a tone is detected, but only if VCO set close to the Morse tone frequency.
; Advantages: Immunity to adjacent channel signal, and amplitude variation.
;
; Timing considerations, how long is a symbol?
; The reference symbol duration is the DIT!
; A DAH symbol is 3 DITs long.
; The space between a pair of symbols is 1 DIT long.
; Characters are groups of symbols and the spaces between them.
; The space between characters is 3 DITs long.
; The space between words is 7 DITs long (according to Rec. ITU-R M.1677).
; There are 50 symbol periods in the reference string: PARIS
; ". _ _ .   . _   . _ .   . .   . . .     "
; so at 12 WPM, we have 600 symbols in 60 seconds, => 0.1 sec / symbol
; @ 36 WPM, the shortest (DIT) symbol period is 33ms long.
;**********************************************************************
		end



