;***************************************************************************
;                                                                     
;	AT Keyboard Box V2.05  (with LCD Display and Keypad)
;	====================================================
;
;	written by Peter Luethi, 06.08.2003, Dresden, Germany
;	http://www.electronic-engineering.ch
;	last update: 17.04.2004
;
;	V2.05:	Added LCDcflag (LCD command/data flag) to comply with
;		latest LCD modules. Changed controller type to flash
;		device, i.e. PIC16F77.
;		(17.04.2004)
;
;	V2.04:	Changed RS232 transmission sub-routine: checks now
;		PIR1,TXIF instead of TXSTA,TRMT
;		(29.10.2003)
;
;	V2.03:	Implemented watchdog timer for user-customization phase.
;		After 12 seconds of inactivity, the user-customization 
;		process is terminated. Then, the default baud rate settings 
;		(9600 baud) are configured, and normal full-duplex operation
;		mode is established.
;		(17.10.2003)
;
;	V2.02:	Fixed issue in RS232 hardware reception service routine.
;		Reads now both data bytes out of double-buffered RX FIFO.
;		Tries to eliminate some sporadic reception hangs under
;		heavy full-duplex traffic scenarios.
;		In case of FIFO overrun errors in the USART receive logic,
;		the service routine performs a partial reset of the USART
;		receive logic. No specific error handling for RS232
;		reception framing errors.
;		(17.10.2003)
;
;	V2.01:	Added dynamic RS232 baud rate configuration through
;		AT keyboard (16.10.2003)
;
;	V2.00:	Added dynamic RS232 baud rate setup (customizable by
;		user through keypad), changed clock frequency from
;		4 MHz to 14.745600 MHz in order to support 1200 baud
;		up to 115200 baud (15.10.2003)
;
;	V1.00:	Initial release (07.10.2003)
;
;	This code and accompanying files may be distributed freely and
;	modified, provided this header with my name and this notice remain
;	intact. Ownership rights remain with me.
;	You may not sell this software without my approval.
;
;	This software comes with no guarantee or warranty except for my
;	good intentions. By using this code you agree to indemnify me from
;	any liability that might arise from its use.
;
;
;	SPECIFICATIONS:
;	===============
;	Processor:			Microchip PIC 16F77 (16C74A)
;	Clock Frequency:		14.745600 MHz (HS mode)
;	Throughput:			3.7 MIPS
;	RS232 Baud Rate:		Customizable by user (BRGH = 0),
;					any setting from 1200 baud - 115200 baud
;	Serial Output:			default setup: 9600 baud, 8 bit,
;					no parity, 1 stopbit
;	Numeric Keypad Features:	Interrupt-based acquisition,
;					direct 8 bit A/D conversion
;	Keyboard Routine Features:	Capability of bi-directional
;					communication between controller
;					and keyboard (keys & LEDs)
;	Acquisition Methodology:	Preemptive, interrupt-based
;					keyboard scan pattern acquisition
;					routine, with decoding to ASCII
;					characters during normal operation
;					(incl. LCD display and RS232 activities)
;	Code Size of entire Program:	1456 instruction words
;	Required Hardware:		AT Keyboard, MAX 232,
;					HD44780 compatible dot matrix LCD
;					(2x16, 2x20 or 2x40 characters)
;	Optional Hardware:		Piezo beeper with decoupling capacitor
;	Required Software:		RS232 terminal
;
;
;	ABSTRACT:
;	=========
;	Fully operating RS232 terminal with completely bi-directional
;	communication capabilities. Multiple input devices are attached,
;	such as an AT keyboard and a small numeric foil-keypad.
;	The application features a dot matrix LCD display to visualize
;	the data received from the RS232 client on the first line, and
;	the characters typed on the locally attached keyboard on the
;	second line. There is also a piezo-beeper for acoustic feedback.
;	This program converts AT keyboard scan patterns to ASCII characters
;	and transmits them afterwards to the target device by using the RS232
;	transmission protocol. Both RS232 data reception and transmission
;	are possible.
;	Dynamic configuration of RS232 baud rate setting at start-up
;	(user-customization with 1200 baud - 115200 baud), with 12 seconds
;	inactivity time-out. In case the time-out applies, the
;	user-customization process terminates with the current setting.
;	Default setting after power-up is 9600 baud.
;	Support of english (QWERTY) and modified swiss-german (QWERTZ)
;	'codepages'. Entry of user data on local AT keyboard and visualization
;	on dot matrix LCD display.
;
;
;	DESCRIPTION:
;	============
;	Developed and tested on Microchip PIC 16F77, previously on PIC 16C74A.
;
;	Using a specific quartz crystal with a frequency of 14.745600 MHz,
;	we can address all baud rates from 1200 baud up to 115200 baud
;	without any error ratio (0.00% deviation!).
;	All aspects of RS232 transmission and reception are handled by PIC
;	hardware resources and interrupts.
;
;	Any key stroke on the AT keyboard connected to the PIC will send the
;	corresponding scan code from the keyboard to the microcontroller.
;	Afterwards, the microcontroller converts the keyboard scan code to
;	ASCII characters and transmits them to the personal computer across
;	the RS232 link.
;	Visualization of received data on the first line, user-entered data
;	on the second line of the dot matrix LCD display. This routine is
;	best used with a 2 line by 20 or 40 character LCD display.
;	This program features also the capability of bi-directional
;	communication between controller and keyboard for configuration
;	purposes and to control the keyboard LEDs.
;
;	The keyboard scan pattern capture and decoding is done by an
;	interrupt service routine. The event, which triggers the interrupt
;	is a falling edge on the keyboard clock line at the KBDclkpin
;	(PORTB,0). The keyboard data (scan code) will be fetched at the
;	KBDdatapin (PORTA,0). The configuration of the KBDclkpin interrupt
;	is done by the KEYBOARDinit macro.
;
;	The numeric foil-keypad is equipped with a specific resistor cascade
;	to decode the values through direct 8 bit A/D conversion using the
;	PIC-internal A/D converter.
;	The advantage is a very low pin usage: Only two pins are necessary
;	for proper detection and decoding of all keypad entries.
;	One pin provides the analog value, the other pin serves for interrupt
;	generation whenever a key of the keypad is touched. The interrupt is
;	used to start the A/D conversion.
;
;	During the interrupt service routine, only a short busy wait (analog
;	settling time) and the A/D conversion - using the internal RC
;	oscillator - is carried out. Before leaving the ISR, the 8 bit A/D
;	result is stored in a specific register and a dedicated flag is set.
;
;	Decoding of the A/D value is done during normal operation (activated
;	by the flag) using two look-up tables. The first look-up table (LUT1)
;	contains the expected 8 bit values of the keypad to check for valid
;	entries. A numeric window of ±3 allows for slight analog deviations
;	during matching. The matching algorithm just scans the entire LUT1
;	until the received keypad A/D result matches a LUT1 entry. The amount
;	of loops carried out in LUT1 determines the position of the
;	corresponding symbol/character in LUT2. At the end, RS232 transmission
;	and LCD display update are carried out.
;
;	RS232 data exchange is carried out by using the internal USART of
;	the PIC 16C74A. RS232 data reception is done on an interrupt
;	based acquisition scheme, provided by the USART.
;
;	Dynamic configuration of RS232 baud rate setting at start-up
;	(user-customization with 1200 baud - 115200 baud). A watchdog timer
;	implemented using TMR1 checks for inactivity during the customization
;	process. After 12 seconds of inactivity, the user-customization process
;	terminates with the current setting. At power-up, the default setting
;	is 9600 baud, which will be configured after the time-out - unless no
;	user-customization takes place.
;
;	For the AT keyboard layout, English and modified Swiss-German
;	'codepages' are supported:
;	QWERTY and QWERTZ keyboard layout
;
;
;	IMPLEMENTED FEATURES:
;	=====================
;	- Dynamic configuration of RS232 baud rate setting at start-up.
;	- Bi-directional communication between microcontroller application
;	  and remote RS232 client by hardware-based RS232 transmission.
;	- Bi-directional communication between microcontroller and keyboard.
;	- Bi-directional communication between microcontroller and LCD
;	  display.
;	- Supports foil-keypad input through direct 8 bit A/D conversion
;	  and look-up table. 
;	- Piezo-beeper for acoustic feedback of keypad entries.
;	- Visualization of received and transmitted characters on local LCD.
;	- Parametrizable LCD display width: constant 'LCDwidth'
;	- Support for all keyboard characters typed with shift button active 
;	  and inactive.
;	- English and modified Swiss-German 'codepages' available
;	  (QWERTY and QWERTZ)
;	  Include the desired keyboard lookup tables at the correct location.
;	- Caps Lock implemented
;	- Num Lock always active
;	- Support of ASCII conversion from direct ALT-DEC entries, e.g.
;	  ALT + 6 + 4 = @   (ALT + [1..3] numbers)
;	- Support of ASCII conversion from direct CTRL-HEX entries, e.g.
;	  CTRL + 3 + F = ?  (CTRL + [1..2] letters/numbers)
;	- ALT-DEC and CTRL-HEX features work for both, keypad and keyboard
;	  numbers, as well as with upper and lower case letters [a..f]
;
;	- Possibility to implement short-cuts or user defined characters
;	  for 'Esc', 'Num Lock', 'Scroll Lock' and 'F1' - 'F12' keys
;
;
;	LIMITATIONS:
;	============
;	- No support for ALT-GR characters.
;	- No support for arrow buttons, 'Home', 'Del', 'PageUp', 'PageDown',
;	  'Insert', 'End' because there exists no character/command
;	  corresponding to the ASCII character map. (But it is possible to
;	  use them for short-cuts or user defined characters, if the special 
;	  code routine (0xE0) is altered.)
;
;
;	NOTE:
;	=====
;	This program needs 'ORG' directives to locate tables within entire
;	memory pages. To allow for slight modifications, the code has not
;	been optimized to the utmost extent regarding program memory
;	placement. This can be carried out using the program memory window
;	of MPLAB showing the hexadecimal representation of the code.
;
;
;	CREDITS:
;	========
;	- Craig Peacock, the author of the excellent page about keyboards
;	  "Interfacing the PC's Keyboard" available at his website:
;	  http://www.beyondlogic.org/keyboard/keybrd.htm
;
;***************************************************************************

;***** COMPILATION MESSAGES & WARNINGS *****

	ERRORLEVEL -207 	; Found label after column 1.
	ERRORLEVEL -302 	; Register in operand not in bank 0.

;***** PROCESSOR DECLARATION & CONFIGURATION *****

	PROCESSOR 16F77
	#include "p16f77.inc"

	;PROCESSOR 16C74A
	;#include "p16c74a.inc"
	
	; embed Configuration Data within .asm File.
	__CONFIG   _CP_OFF & _WDT_OFF & _PWRTE_ON & _HS_OSC & _BODEN_ON

;***** MEMORY STRUCTURE *****

	;ORG     0x00	processor reset vector, declared below
	;ORG     0x04	interrupt service routine, declared below

;***** PARAMETERIZATION *****

	CONSTANT BEEP_ENABLE =	d'1'	; Piezo beeper, default: enabled
	CONSTANT KEYPAD_DELAY =	0x50	; busy wait
				;0x40	; busy wait (64 us @ 16 MHz)
				;0x15	; busy wait (64 us @ 4 MHz)
				;0x10	; busy wait (49 us @ 4 MHz)
				;0x07	; busy wait (22 us @ 4 MHz)
	CONSTANT KEYPAD_SIZE =	d'12'	; amount of keypad buttons
	CONSTANT KEYPAD_WINDOW = d'6'	; window size for A/D value matching
	CONSTANT LCDwidth =	d'40'	; define character width of LCD
					; display
	CONSTANT KBD_conf_value = d'7'	; keyboard configuration time-out value
	CONSTANT WDT_CNT_value = d'85'	; watchdog timer value for user-customization
					; (equals 12 seconds time-out)
;***** PORT DECLARATION *****

	#define	KBDdatapin  PORTA,0x04	; keyboard data input port
	#define	KBDdatatris TRISA,0x04
	#define	KBDclkpin   PORTB,0x00	; keyboard clock input port
	#define	KBDclktris  TRISB,0x00  ; @ INTF interrupt source (RB0)

	LCDport	equ	PORTD
	LCDtris	equ	TRISD

	#define	PADOUTpin  PORTA,0x00	; numeric keypad analog input
	#define	PADOUTtris TRISA,0x00
	#define	PADIRQpin  PORTB,0x07	; numeric keypad interrupt pin
	#define	PADIRQtris TRISB,0x07	; @ RBIF interrupt source (RB4:7)

	#define	BEEPport   PORTE,0x00	; output port for piezo beeper
	#define	BEEPtris   TRISE,0x00

	; debug LEDs
	;#define	DEBUG0tris	TRISC,0x00	; Port C !
	;#define	DEBUG1tris	TRISC,0x01
	;#define	DEBUG2tris	TRISC,0x02
	;#define	DEBUG3tris	TRISC,0x03
	;#define	DEBUG4tris	TRISC,0x04
	;#define	DEBUG5tris	TRISC,0x05
	;#define	DEBUG0		PORTC,0x00	; Port C !
	;#define	DEBUG1		PORTC,0x01
	;#define	DEBUG2		PORTC,0x02
	;#define	DEBUG3		PORTC,0x03
	;#define	DEBUG4		PORTC,0x04
	;#define	DEBUG5		PORTC,0x05

;***** CONSTANT DECLARATION *****

	CONSTANT BASE = 0x20	; base address of user file registers
	CONSTANT LF =	d'10'	; line feed
	CONSTANT CR =	d'13'	; carriage return
	CONSTANT TAB =	d'9'	; tabulator
	CONSTANT BS =	d'8'	; backspace
	CONSTANT SL =	d'0'	; control bit for keyboard scroll lock LED
	CONSTANT NL =	d'1'	; control bit for keyboard num lock LED
	CONSTANT CL =	d'2'	; control bit for keyboard caps lock LED

;***** REGISTER DECLARATION *****

	TEMP1	set	BASE+d'0'	; Universal temporary register
	TEMP2	set	BASE+d'1'	; ATTENTION !!!
	TEMP3	set	BASE+d'2'	; They are used by various modules.
	TEMP4	set	BASE+d'3'	; If you use them, make sure not to use
	TEMP5	set	BASE+d'4'	; them concurrently !
	TEMP6	set	BASE+d'5'	; Don't use them in the ISR!
	TEMP7	set	BASE+d'6'

	FLAGreg	equ	BASE+d'7'	; register containing various flags
	FLAGreg2 equ	BASE+d'8'
	FLAGreg3 equ	BASE+d'9'
	#define	RSflag	FLAGreg,0x00	; RS232 data reception flag
	#define	RELflag	FLAGreg,0x01	; release flag (0xF0)
	#define	SHIflag	FLAGreg,0x02	; shift flag (0x12 / 0x59)
	#define	SPEflag	FLAGreg,0x03	; special code flag (0xE0)
	#define	CAPflag	FLAGreg,0x04	; caps lock flag (0x0D)
	#define	ALTflag	FLAGreg,0x05	; ALT flag (0x11)
	#define	CTRLflag FLAGreg,0x06	; CTRL flag (0x14)
	#define	KBDflag	FLAGreg,0x07	; keyboard data reception flag
	#define	KBDtxf	FLAGreg2,0x00	; keyboard transmission flag
	#define	PARITY	FLAGreg2,0x01	; parity bit to be calculated
	#define	KBDexpf	FLAGreg2,0x02	; keyboard expect function flag
	#define	LCDbusy	FLAGreg2,0x03	; LCD busy flag
	#define	LCDcflag FLAGreg2,0x04	; LCD command/data flag
	#define	LCD_ln	FLAGreg2,0x05	; LCD line flag: 0 = line 1, 1 = line 2
	#define BCflag	FLAGreg2,0x06	; blank checker for preceding zeros
	#define KPflag	FLAGreg2,0x07	; numeric foil-keypad reception flag
	#define CUST_fl	FLAGreg3,0x00	; user-customization flag for RS232
	#define RX2flag	FLAGreg3,0x01	; two RS232 bytes received
	#define WDTflag FLAGreg3,0x02	; watchdog time-out flag

	RXreg	equ	BASE+d'10'	; first RS232 RX-Data register
	RXreg2	equ	BASE+d'11'	; second RS232 RX-Data register
	RXtemp	equ	BASE+d'12'	; intermediate data register for LCD output

	W_TEMP	equ	BASE+d'13'	; context register (ISR)
	STATUS_TEMP equ	BASE+d'14'	; context register (ISR)
	PCLATH_TEMP equ	BASE+d'15'	; context register (ISR)
	FSR_TEMP equ	BASE+d'16'	; context register (ISR)
	ISR_TEMP1 equ	BASE+d'17'	; temporary registers for ISR

	KBDcnt	equ	BASE+d'18'	; IRQ based keyboard scan pattern counter
	KBD	equ	BASE+d'19'	; keyboard scan code & ascii data register
	KBDcopy	equ	BASE+d'20'	; keyboard scan code register
	KBDleds	equ	BASE+d'21'	; keyboard LEDs' status register
	KBDexpval equ	BASE+d'22'	; temporary KBD expected response value
	LCDpos1	equ	BASE+d'23'	; LCD output position counter
	LCDpos2	equ	BASE+d'24'	; LCD output position counter

	CTRLcnt	 equ	BASE+d'25'	; counter for CTRL/ALT stuff
	CTRLreg1 equ	BASE+d'26'	; storage registers for ALT-DEC and
	CTRLreg2 equ	BASE+d'27'	; CTRL-HEX conversion routines
	CTRLreg3 equ	BASE+d'28'

	KPval equ	BASE+d'29'	; A/D value for keypad decoding

	RS232_CUST equ	BASE+d'30'	; register for RS232 communication setup
	CUST_CNT equ	BASE+d'31'	; counter for customization routine
	CUST_POS equ	BASE+d'32'	; counter for current table position
	WDT_CNT	equ	BASE+d'33'	; time-out counter for customization

;***** INCLUDE FILES *****

	ORG	0x430
	#include "..\..\m_bank.asm"
	#include "..\..\m_wait.asm"
	#include "..\..\m_lcd_bf.asm"
	IF BEEP_ENABLE == 1		; conditional assembly
	  #include "..\..\m_beep.asm"	; Piezo beeper
	ENDIF

;***** MACROS *****

KEYPADinit macro
	BANK1
	bsf	PADOUTtris	; set numeric keypad to (analog) input
	bsf	PADIRQtris	; set numeric keypad interrupt pin to input
	BANK0
	;movlw	b'01000001'	; select Fosc/8, CH0, A/D ON
	movlw	b'11000001'	; select RC Osc, CH0, A/D ON
	movwf	ADCON0
	bcf	INTCON,RBIF	; ensure interrupt flag is cleared
	bsf	INTCON,RBIE	; enable PORTB4:7 change interrupts
	endm	

KEYBOARDinit macro
	BANK1
	bsf	KBDclktris	; set keyboard clock line to input explicitely
	bsf	KBDdatatris	; set keyboard data line to input explicitely
	bcf	OPTION_REG,INTEDG ; keyboard interrupt on falling edge
	BANK0
	bcf	INTCON,INTF	; ensure interrupt flag is cleared
	bsf	INTCON,INTE	; enable RB0/INT interrupt
	endm

RS232init macro			; call with customized setup value in w (RS232_CUST)
	BANK1			; Asynchronous USART assignment:
	movwf	SPBRG		; adjust baud rate
	bcf	TXSTA,BRGH	; BRGH = 0
	BANK0
	bsf	RCSTA,SPEN	; enable serial port
	BANK1
	bsf	PIE1,RCIE	; enable serial reception interrupt
	bsf	TXSTA,TXEN	; enable serial transmission
	BANK0
	bsf	INTCON,PEIE	; enable peripheral interrupts
	bsf	RCSTA,CREN	; enable continuous reception
	endm

SEND	macro	send_char
	movlw	send_char
	call	_RSxmit
	endm

SENDw	macro
	call	_RSxmit
	endm

KBDcmd	macro	_KBDcmd		; this macro transmits the literal _KBDcmd
	movlw	_KBDcmd		;  to the AT keyboard
	movwf	KBD
	call	ODDpar		; calculate odd parity of TX data
	call	KBDcomd		; transmit contents of KBD reg. to keyboard
	endm

KBDcmdw	macro			; this macro transmits the value of w to the
	movwf	KBD		;  AT keyboard
	call	ODDpar		; calculate odd parity of TX data
	call	KBDcomd		; transmit contents of KBD reg. to keyboard
	endm

KBDexp	macro	_KBDresp	; keyboard expect function: busy wait for kbd response
	movlw	_KBDresp	; load expected kbd response
	call	KBDexpfun
	endm

;***** SUBROUTINES *****

WDT_SETUP ;*** sub-routine for IRQ-based WDT timer using TMR1 ***
	movwf	WDT_CNT		; set self-defined watchdog (customization time-out)
	bcf	WDTflag		; clear WDT time-out flag
	clrf	TMR1L
	clrf	TMR1H
	bcf	PIR1,TMR1IF	; reset TMR1 interrupt flag
	BANK1
	bsf	PIE1,TMR1IE	; enable TMR1 interrupt
	BANK0
	bsf	INTCON,PEIE	; enable peripheral interrupts
	; configure & enable TMR1:
	; prescaler = b'11', OSCEN = 0, _T1SYNC = 0, TMR1CS = 0, TMR1ON = 1
	movlw	b'00110001'	
	movwf	T1CON
	RETURN

WDT_STOP ; stop and reset watchdog timer
	BANK1
	bcf	PIE1,TMR1IE	; disable TMR1 interrupt
	BANK0
	bcf	T1CON,TMR1ON	; stop TMR1
	bcf	WDTflag		; reset watchdog flag
	RETURN

_RSxmit	;*** check transmit buffer register empty flag ***
_RSbusy	btfss	PIR1,TXIF	; check, if TXREG is empty (TXIF = 1)
	goto	_RSbusy		; TXIF = 0, wait
	movwf	TXREG		; send next char
	RETURN

KBDcomd	;*** host to keyboard command transmission ***
	bcf	INTCON,INTE	; disable RB0/INT interrupt
	BANK1			; hold keyboard (with kbd clk low):
	bcf	KBDclktris	; set clk line to output
	bcf	KBDdatatris	; set data line to output
	BANK0
	bcf	KBDclkpin	; set keyboard clk line low
	bcf	KBDdatapin	; set keyboard data line low
	movlw	0x20		; load temporary counter
	movwf	TEMP1
_KBDtx1	decfsz	TEMP1,F		; wait loop: approx. 60 us @ 4 MHz
	goto	_KBDtx1
	clrf	KBDcnt		; init kbd scan pattern acquisition counter
	BANK1
	bsf	KBDclktris	; release keyboard clk line, set to input
	BANK0
	;bsf	DEBUG0		;#######################
	bsf	KBDtxf		; set keyboard TX flag
	bcf	INTCON,INTF	; clear RB0/INT interrupt flag
	bsf	INTCON,INTE	; re-enable RB0/INT interrupt
_KBDtx2	btfsc	KBDtxf		; wait loop: poll for completion
	goto	_KBDtx2		; not yet completed, loop
	;bcf	DEBUG0		;#######################
	RETURN

ODDpar	;*** odd parity = NOT(XOR(bits[0..7])) ***
	movwf	TEMP3		; target, which the parity bit is built from
	movlw	0x08
	movwf	TEMP4		; loop counter
	clrf	TEMP5		; the ones' counter / bit counter
_ODDpar	btfsc	TEMP3,0x00	; check for ones
	incf	TEMP5,F		; increment ones' counter
	rrf	TEMP3,F
	decfsz	TEMP4,F		; decrement loop counter
	goto	_ODDpar
	bcf	PARITY		; clear parity
	btfss	TEMP5,0x00	; check ones' counter for even value
	bsf	PARITY		; if even, set parity bit (= odd parity)
	RETURN

KBDexpfun ;*** keyboard expect function: busy wait for kbd response ***
	movwf	KBDexpval	; load expected kbd response to KBDexpval
	bsf	KBDexpf		; set flag
_KBDexp	btfsc	WDTflag		; check for watchdog expiration
	goto	_KBDexp_timeout	; abort in case of time-out
	btfsc	KBDexpf		; wait loop: poll for completion
	goto	_KBDexp		; not yet completed, loop
	RETURN
_KBDexp_timeout
	bcf	KBDexpf		; reset flag in case of time-out
	clrf	KBDcnt
	;bsf	DEBUG4		; ### DEBUGGING ###
	RETURN

RSdisplay ;*** LCD display routine for received RS232 characters ***
	; first byte in RXreg
	movfw	RXreg		; retrieve data
	movwf	RXtemp		; store data
	call	_RSdisp		; call output sub-routine
	btfss	RX2flag		; check for second data byte received
	goto	RSdispEND
	; second byte in RXreg2, i.e. RX2flag = 1
	movfw	RXreg2		; retrieve data
	movwf	RXtemp		; store data
	call	_RSdisp		; call output sub-routine
	bcf	RX2flag		; reset flag
	btfss	RCSTA,OERR	; check for RX overrun (overrun error bit)
	goto	RSdispEND
	; buffer overrun, severe error, reset USART RX logic
	bcf	RCSTA,CREN	; reset USART RX logic, clear RCSTA,OERR
	bcf	RCSTA,FERR	; reset framing error bit
	bsf	RCSTA,CREN	; re-enable continuous reception
RSdispEND
	BANK1
	bsf	PIE1,RCIE	; re-enable USART reception interrupt
	BANK0			;  (interrupt flag already cleared in ISR)
	RETURN

_RSdisp	; output sub-routine for serial data received
	movfw	RXtemp		; check first for backspace
	BNEQ	0x08, _RSclr	; no backspace, branch to next check
	movfw	LCDpos1		; else, decrement LCD cursor position
	bz	_RSend		; if zero, do nothing & exit
	decf	LCDpos1,F	; decrement cursor position
	movfw	LCDpos1		; load cursor position to w
	iorlw	b'10000000'	; mask
	call	LCDcomd		; call LCD command sub-routine 
	LCDchar	' '		; erase with blank character
	goto	_RSend		; exit
_RSclr	movfw	LCDpos1		; check now for line end: get LCD position counter
	BRS	LCDwidth, _RSdsp1 ; if not at the line end, branch to next check
	bcf	LCD_ln		; line flag = 0 (LCD line 1)
	call	LCDclrline	; call LCD clear line sub-routine
_RSdsp1	movfw	LCDpos1		; load cursor position to w
	iorlw	b'10000000'	; mask
	call	LCDcomd		; call LCD command sub-routine to place cursor
	movfw	RXtemp		; check for carriage return
	BRG	0x0D, _RSdsp2	; branch on w > 0x0D
	skpz			; skip on zero: it was a carriage return
	goto	_RSend		; else terminate
_RScr	bcf	LCD_ln		; line flag = 0 (LCD line 1)
	call	LCDclrline	; call LCD clear line sub-routine
	goto	_RSend		; exit
_RSdsp2	movfw	RXtemp
	LCDw			; display RS232 data on LCD
	incf	LCDpos1,F	; increment LCD position counter
_RSend	bcf	RSflag		; reset RS232 data reception flag
	RETURN

KEYPADout ;*** numeric foil-keypad output routine (LCD display & RS232) ***
	IF BEEP_ENABLE == 1	; conditional assembly
	  BEEP	d'240', d'4'	; acoustic feedback
	ENDIF
	movlw	KEYPAD_SIZE-0x1	; get amount of keypad buttons
	movwf	TEMP6		; prepare loop counter
_KPlut1	; loop through keypad look-up table 1
	movlw	HIGH KeypadTable1 ; get correct page for PCLATH
	movwf	PCLATH		; prepare right page bits for table read
	movfw	TEMP6		; retrieve loop counter value
	call	KeypadTable1	; call lookup table
	addlw	0x0 - KEYPAD_WINDOW/2 ; w - WINDOW/2
	movwf	TEMP7		; store retrieved value in temp register
	subwf	KPval,w		; KPval - w = w
	bnc	_KPreloop	; no carry if result negative, branch
	movfw	TEMP7
	subwf	KPval,w		; KPval - w = w
	BRES	KEYPAD_WINDOW, _KPlut2 ; A/D value in window, branch
_KPreloop decfsz TEMP6,f	; decrement loop counter, exit if zero
	goto	_KPlut1		; loop again
_KPlut2	; get now corresponding symbol from keypad look-up table 2
	movlw	HIGH KeypadTable2 ; get correct page for PCLATH
	movwf	PCLATH		; prepare right page bits for table read
	movfw	TEMP6		; retrieve loop counter value
	call	KeypadTable2	; call lookup table
	btfsc	CUST_fl		; if set, user-customization in progress, branch
	goto	USER_CUST1
	; send symbol now to attached I/O devices: LCD & RS232
	SENDw			; RS232 transmission of symbol in w
	movwf	KBD		; to be able to call LCDout_line2
	call LCDout_line2	; shared subroutine for LCD output
	bcf	KPflag		; reset keypad reception flag
	bsf	INTCON,RBIE	; re-enable PORTB4:7 change interrupts
	RETURN

USER_CUST1 ;*** user-customization of RS232 settings (baud rate setup) ***
	movwf	TEMP6		; store value
	BNEQ	a'*', _CUST1_SET ; if no '*', branch to next check
	call	_CUST1_CHANGE	; call common 'change' sub-routine
	goto	_CUST1_END	; now terminate
_CUST1_SET
	movfw	TEMP6
	BNEQ	a'#', _CUST1_END ; if no '#', branch to exit
	bcf	CUST_fl		; clear flag to indicate setup is finished
_CUST1_END ; termination
	bcf	KPflag		; reset keypad reception flag
	movlw	WDT_CNT_value	; load watchdog counter value
	movwf	WDT_CNT		; reset self-defined watchdog (customization time-out)
	bsf	INTCON,RBIE	; re-enable PORTB4:7 change interrupts
	RETURN

_CUST1_CHANGE
	; RS232_CUST =   1 (115200), 3 (57600),  5 (38400),  11 (19200)
	; CUST_CNT   =   7           6           5            4        
	; RS232_CUST =  23 (9600),  47 (4800),  95 (2400),  191 (1200)
	; CUST_CNT   =   3           2           1            0
	; change configuration value
	clrf	CUST_POS	; reset table position counter
	incf	CUST_CNT,F	; increment counter to change configuration
	movfw	CUST_CNT
	BRES	0x7, _CUST1_C	; branch if still in [0..7]
	clrf	CUST_CNT	; it was > 7, reset to 0
_CUST1_C ;*** calculate table position of the current display output to change ***
	movfw	CUST_CNT
	movwf	TEMP7		; get configuration counter value
	bz	_CUST1_D	; skip loop in case of zero (1200 baud)
_CUST1_LOOP1
	movlw	d'4'		; delta
	addwf	CUST_POS,F	; adjust offset
	decfsz	TEMP7,F		; decrement loop counter
	goto	_CUST1_LOOP1	; loop again
	; on exit, we have the correct offset in CUST_POS for CUST_Table1
_CUST1_D ;*** adjust LCD output (through look-up table) ***
	LCD_DDAdr 0x46		; set LCD cursor position
	movlw	d'4'		; prepare loop counter
	movwf	TEMP7
_CUST1_LOOP2
	movlw	HIGH CUST_Table1 ; get correct page for PCLATH
	movwf	PCLATH		; prepare right page bits for table read
	movfw	CUST_POS	; get current table position
	call	CUST_Table1	; look-up character to display
	LCDw			; display character
	incf	CUST_POS,F	; increment position counter
	decfsz	TEMP7,F		; decrement loop counter
	goto	_CUST1_LOOP2	; loop again
	; on exit, we have updated the LCD display content
	;*** adjust RS232 communication configuration value ***
	movlw	HIGH CUST_Table2 ; get correct page for PCLATH
	movwf	PCLATH		; prepare right page bits for table read
	movfw	CUST_CNT
	call	CUST_Table2	; retrieve baud rate value for initialization
	movwf	RS232_CUST	; => store in intermediate config register
	RETURN

LCDout_line2 ;*** shared LCD subroutine for AT keyboard and foil-keypad ***
	; Pre:  Character stored in register KBD
	; Post: Output on LCD line 2
	; check for necessary clearing of LCD line 2
	movfw	LCDpos2		; get LCD position counter
	sublw	LCDwidth - 1	; w = LCDwidth - 1 - w
	bc	_LCDout		; branch if res positive or zero
	bsf	LCD_ln		; line flag = 1 (LCD line 2)
	call	LCDclrline	; call LCD clear line subroutine
_LCDout	movfw	LCDpos2		; load cursor position to w
	addlw	0x40		; add offset (LCD line 2)
	iorlw	b'10000000'	; mask
	call	LCDcomd		; call LCD command subroutine to place cursor
	movfw	KBD		; retrieve character
	LCDw			; display keyboard character on LCD
	incf	LCDpos2,F	; increment LCD position counter
	RETURN

LCDclrline ;*** clears either LCD line 1 or 2 ***
	movlw	LCDwidth	; get LCD character width
	btfsc	LCD_ln		; check LCD line flag
	goto	_clrln2		; if flag is set, clear LCD line 2
_clrln1	movwf	LCDpos1		; store LCD char width in position counter
	LCD_DDAdr 0x00		; move cursor to beginning of first line
_clr1	LCDchar	' '		; put subsequent blanks on LCD to clear line
	decfsz	LCDpos1,F	; decrement counter
	goto	_clr1
	LCD_DDAdr 0x00		; reset cursor to beginning of line
	clrf	LCDpos1		; reset LCD position counter
	RETURN
_clrln2	movwf	LCDpos2		; store LCD char width in position counter
	LCD_DDAdr 0x40		; move cursor to beginning of second line
_clr2	LCDchar	' '		; put subsequent blanks on LCD to clear line
	decfsz	LCDpos2,F	; decrement counter
	goto	_clr2
	LCD_DDAdr 0x40		; reset cursor to beginning of line
	clrf	LCDpos2		; reset LCD position counter
	RETURN


;***** INTERRUPT SERVICE ROUTINE *****

	ORG	0x04		; interrupt vector location

ISR	;************************
	;*** ISR CONTEXT SAVE ***
	;************************

	bcf	INTCON,GIE	; disable all interrupts
	btfsc	INTCON,GIE	; assure interrupts are disabled
	goto	ISR
	movwf	W_TEMP		; context save: W
	swapf	STATUS,W	; context save: STATUS
	movwf	STATUS_TEMP	; context save
	clrf	STATUS		; bank 0, regardless of current bank
	movfw	PCLATH		; context save: PCLATH
	movwf	PCLATH_TEMP	; context save
	clrf	PCLATH		; page zero, regardless of current page
	bcf	STATUS,IRP	; return to bank 0
	movfw	FSR		; context save: FSR
	movwf	FSR_TEMP	; context save
	;*** context save done ***

	;**************************
	;*** ISR MAIN EXECUTION ***
	;**************************

	;*** check origin of interrupt ***
	btfsc	INTCON,INTF	; check for RB0/INT interrupt
	goto	_ISR_KBD	; if set, there was an AT keyboard interrupt

	btfsc	INTCON,RBIF	; check for RB[7:4] port change interrupt
	goto	_ISR_KEYPAD	; if set, there was a keypad stroke

	btfsc	PIR1,RCIF	; check for USART interrupt
	goto	_ISR_RS232	; if set, there was an USART interrupt

	btfsc	PIR1,TMR1IF	; check for TMR1 interrupt
	goto	_ISR_TMR1	; if set, there was a TMR1 interrupt
	goto	ISRend		; unexpected IRQ, terminate execution of ISR

	;**************************************************************
	;*** TMR1 INTERRUPT - WATCHDOG TIMER FOR USER-CUSTOMIZATION ***
	;**************************************************************
_ISR_TMR1
	decf	WDT_CNT,F	; decrement watchdog counter
	bnz	_ISR_TMR1_A	; if not zero, exit	
	; watchdog has timed out:
	bsf	WDTflag		; set WDT time-out flag
	bcf	CUST_fl		; prepare abort of user-customization:
				;  clear flag to indicate setup has to finish
_ISR_TMR1_A
	bcf	PIR1,TMR1IF	; reset TMR1 interrupt flag
	goto	ISRend		; exit ISR

	;******************************
	;*** RS232 DATA ACQUISITION ***
	;******************************
_ISR_RS232
	movfw	RCREG		; get RS232 data
	movwf	RXreg
	bsf	RSflag		; enable RS232 data reception flag
	BANK1			;  (for display routine)
	bcf	PIE1,RCIE	; disable USART reception interrupt
	BANK0			;  (will be re-enabled in normal subroutine)
	goto	_RS232end	; exit ISR

	;*******************************
	;*** NUMERIC KEYPAD DECODING ***
	;*******************************
_ISR_KEYPAD
	btfss	PADIRQpin	; check if active one
	goto	_KEYPADend	; else, exit ISR
	bcf	INTCON,RBIE	; disable PORTB4:7 change interrupts
	movlw	KEYPAD_DELAY	; busy wait (64 us @ 4 MHz)
	movwf	ISR_TEMP1
_KPloop1 decfsz	ISR_TEMP1,f
	goto	_KPloop1
	; A/D input charge time completed, continue
	bsf	ADCON0,GO	; turn conversion ON
_KPloop2 btfsc	ADCON0,GO	; check A/D status flag
	goto	_KPloop2	; wait until the end of conversion
	movfw	ADRES		; fetch result of A/D conversion
	movwf	KPval		; store A/D keypad value
	bsf	KPflag		; set keypad reception flag
	goto	_KEYPADend	; exit ISR

	;****************************
	;*** AT KEYBOARD DECODING ***
	;****************************
_ISR_KBD ;*** check origin of keyboard interrupt ***
	btfss	KBDtxf		; check keyboard TX flag
	goto	_ISR_KBDacq	; if cleared, keyboard data acquisition,
	;goto	_ISR_KBDxmit	; else keyboard data transmission

	;******************************************
	;*** HOST TO KEYBOARD DATA TRANSMISSION ***
	;******************************************
_ISR_KBDxmit
	;*** data transmission ***
	movfw	KBDcnt		; get kbd scan pattern acquisition counter
	sublw	d'7'		; w = d'7' - KBDcnt (*)
	bnc	_KBDparo	; branch if negative (carry == 0)
	btfss	KBD,0x00	; serial transmission of keyboard data
	bcf	KBDdatapin
	btfsc	KBD,0x00
	bsf	KBDdatapin
	rrf	KBD,F		; rotate right keyboard TX data register
	goto	_INCF		; exit

	;*** parity transmission ***
_KBDparo movfw	KBDcnt		; get kbd scan pattern acquisition counter
	sublw	d'8'		; w = d'8' - KBDcnt
	bnc	_KBDrel		; branch if negative (carry == 0)
	btfss	PARITY		; put parity bit on keyboard data line
	bcf	KBDdatapin
	btfsc	PARITY
	bsf	KBDdatapin
	goto	_INCF		; exit

	;*** data and parity transmission completed, turn around cycle ***
_KBDrel	movfw	KBDcnt		; get kbd scan pattern acquisition counter
	sublw	d'9'		; w = d'9' - KBDcnt
	bnc	_KBDack		; branch if negative (carry == 0)
	BANK1
	bsf	KBDdatatris	; release keyboard data line, set to input
	BANK0
	goto	_INCF		; exit

_KBDack	movfw	KBDcnt		; get kbd scan pattern acquisition counter
	sublw	d'11'		; w = d'11' - KBDcnt
	bnc	_INCF		; exit if negative (carry == 0)
	clrf	KBDcnt		; reset kbd scan pattern acquisition counter
	bcf	KBDtxf		; clear keyboard transmission flag
	goto	_KBDend

	;*****************************************
	;*** KEYBOARD SCAN PATTERN ACQUISITION ***
	;*****************************************
_ISR_KBDacq
	;*** check start bit ***
	tstf	KBDcnt		; check
	bnz	_KBDdat		; branch on no zero
	btfsc	KBDdatapin	; test start bit of keyboard data input
	goto	_KBDabort1	; no valid start bit, abort
	goto	_INCF		; exit

	;*** keyboard scan pattern acquisition ***
_KBDdat	movfw	KBDcnt		; get kbd scan pattern acquisition counter
	sublw	d'8'		; w = d'8' - KBDcnt (*)
	bnc	_KBDpari	; branch if negative (carry == 0)
	btfss	KBDdatapin	; get keyboard data input
	bcf	KBD,0x07	; (bit operations do not alter zero flag)
	btfsc	KBDdatapin
	bsf	KBD,0x07	
	bz	_INCF		; exit on zero (zero flag still valid from (*))
	rrf	KBD,F		; do this only 7 times
	goto	_INCF		; exit

	;*** ignore parity bit ***
_KBDpari movfw	KBDcnt		; get kbd scan pattern acquisition counter
	sublw	d'9'		; w = d'9' - KBDcnt
	bnc	_KBDstp		; branch if negative (carry == 0)
	goto	_INCF		; exit

	;*** check stop bit ***
_KBDstp	btfss	KBDdatapin	; check if stop bit is valid
	goto	_KBDabort2	; if not set, abort
	btfsc	KBDexpf		; check for active expect function flag
	goto	_KBDchk		; if set, goto expect value checking routine
	bsf	KBDflag		; else set reception flag to decode KBD
	;*** stall keyboard ***
	; to prevent the arrival of more data before having finished decoding
	BANK1			; hold keyboard (with kbd clk low):
	bcf	KBDclktris	; set clk line to output
	BANK0
	bcf	KBDclkpin	; set keyboard clk line low (stall)
	;*** disable RB0/INT interrupt line ***
	bcf	INTCON,INTE	; disable RB0/INT interrupt
	goto	_KBDterm	; terminate successfully
_KBDchk	movfw	KBDexpval
	subwf	KBD,W		; w = KBD - w
	bnz	_KBDabort3	; check zero flag, branch should never occur
	bcf	KBDexpf		; clear flag
	clrf	KBDcnt		; reset kbd scan pattern acquisition counter
	goto	_KBDend		; exit ISR successfully

_KBDabort1 ;bsf	DEBUG1		; ### DEBUGGING ###
	goto	_KBDabort
_KBDabort2 ;bsf	DEBUG2		; ### DEBUGGING ###
	goto	_KBDabort
_KBDabort3 ;bsf	DEBUG3		; ### DEBUGGING ###
_KBDabort clrf	KBD		; abort / invalid data
	;bcf	KBDexpf		; reset flag	### DEBUGGING ###
_KBDterm clrf	KBDcnt		; reset kbd scan pattern acquisition counter
	goto	_KBDend		; terminate execution of keyboard ISR	

	;***********************************
	;*** CLEARING OF INTERRUPT FLAGS ***
	;***********************************
	; NOTE: Below, I only clear the interrupt flags! This does not
	; necessarily mean, that the interrupts are already re-enabled.
	; Basically, interrupt re-enabling is carried out at the end of
	; the corresponding service routine in normal operation mode.
	; The flag responsible for the current ISR call has to be cleared
	; to prevent recursive ISR calls. Other interrupt flags, activated
	; during execution of this ISR, will immediately be served upon
	; termination of the current ISR run.
_RS232end
	bcf	PIR1,RCIF	; clear USART RX interrupt flag
	goto	ISRend		; terminate execution of ISR

_KEYPADend
	bcf	INTCON,RBIF	; clear RB[7:4] port change interrupt flag
	goto	ISRend		; terminate execution of ISR

_INCF	incf	KBDcnt,F	; increment acquisition counter
_KBDend	bcf	INTCON,INTF	; clear RB0/INT interrupt flag
	;goto	ISRend		; terminate execution of ISR

	;*****************************************
	;*** ISR TERMINATION (CONTEXT RESTORE) ***
	;*****************************************

ISRend	movfw	FSR_TEMP	; context restore
	movwf	FSR		; context restore
	movfw	PCLATH_TEMP	; context restore
	movwf	PCLATH		; context restore
	swapf	STATUS_TEMP,W	; context restore
	movwf	STATUS		; context restore
	swapf	W_TEMP,F	; context restore
	swapf	W_TEMP,W	; context restore
	RETFIE			; enable global interrupt (INTCON,GIE)

;***** END OF INTERRUPT SERVICE ROUTINE *****


;***** KEYBOARD SCAN PATTERN DECODING SUBROUTINE *****

KBDdecode
	;**********************************************************
	;*** KEYBOARD SCAN CODE PRE-DECODING, SET / CLEAR FLAGS ***
	;**********************************************************

	;*** check key release scan code (F0) ***
	movfw	KBD		; get scan code
	movwf	KBDcopy		; make backup of scan code for later use
	sublw	0xF0		; check if FO has been sent:
	bnz	_KBD_1		; branch if no 'release' scan code occured
	bsf	RELflag		; set key release flag if 'release' occured
	bcf	SPEflag		; clear special code flag always on release
	goto	_ClrStall	; abort with nothing to display
_KBD_1	btfss	RELflag		; check release flag, exit if cleared:
	goto	_KBD_2		; release flag has not been set, branch
	;*** release flag has been set / key release is in progress: ***
	bcf	RELflag		; clear key release flag
	;*** if release of SHIFT key, clear shift flag: ***
	movfw	KBD		; check left shift button (0x12):
	sublw	0x12		; subtract, check if zero
	bz	_clrSHI		; if zero, branch (to next check)
	movfw	KBD		; check right shift button (0x59):
	sublw	0x59		; subtract, check if zero
	skpnz			; skip on non zero
_clrSHI	bcf	SHIflag		; clear shift flag
	;*** check for CAPS LOCK activity: ***
	movfw	KBD		; check caps lock key release:
	sublw	0x58		; subtract, check if zero
	bnz	_clrALT		; if not zero, branch (to next check)
	btfss	CAPflag		; check flag, clear flag if set:
	goto	_setCAP		; flag has not been set, branch
	bcf	CAPflag		; clear caps lock flag
	;*** switch keyboard's caps lock LED off ***
	KBDcmd	0xED		; keyboard LEDs' control command
	KBDexp	0xFA		; expect keyboard acknowledge (FA)
	bcf	KBDleds,CL	; clear caps lock bit
	movfw	KBDleds		; load keyboard LEDs' status
	KBDcmdw			; send keyboard LEDs' control data
	KBDexp	0xFA		; expect keyboard acknowledge (FA)
	goto	_ClrStall	; abort with nothing to display
_setCAP	bsf	CAPflag		; set caps lock flag
	;*** switch keyboard's caps lock LED on ***
	KBDcmd	0xED		; keyboard LEDs' control command
	KBDexp	0xFA		; expect keyboard acknowledge (FA)
	bsf	KBDleds,CL	; set caps lock bit
	movfw	KBDleds		; load keyboard LEDs' status
	KBDcmdw			; send keyboard LEDs' control data
	KBDexp	0xFA		; expect keyboard acknowledge (FA)
	goto	_ClrStall	; abort with nothing to display
	;*** check for ALT activity: ***
_clrALT	movfw	KBD		; check ALT key release:
	sublw	0x11		; subtract, check if zero
	bnz	_clrCTRL	; if not zero, branch (to next check)
	bcf	ALTflag		; clear flag
	goto	_ALTdec		; goto ALT-DEC-Entry conversion/display routine
	;*** check for CTRL activity: ***
_clrCTRL movfw	KBD		; check CTRL key release:
	sublw	0x14		; subtract, check if zero
	bnz	_ClrStall	; if not zero, branch / exit
	bcf	CTRLflag	; clear flag
	goto	_CTRLhex	; goto CTRL-HEX-Entry conversion/display routine


	;****************************************************
	;* The following table has to be located within one *
	;* page to allow for correct lookup functionality.  *
	;* Therefore, additional ISR code has been moved    *
	;* further down.                                    *
	;****************************************************

	;*********************************************************************
	;* LOOKUP TABLE FOR KEYBOARD-SCAN-CODE TO ASCII-CHARACTER CONVERSION *
	;*********************************************************************

	ORG	0x220 ;(280)			; if necessary, move table

	#include "..\tables\eng_main.asm"  	; English 'codepage'
	;#include "..\tables\ger_main.asm"  	; modified Swiss-German 'codepage'

	;****************************************************
	;* The following code belongs also to the interrupt *
	;* service routine and has been moved down to this  *
	;* place to allow the main keyboard decode lookup   *
	;* table to be located within one page.             *
	;* The code below may be arranged on another page,  *
	;* but that doesn't matter.                         *
	;****************************************************

	;********************************
	;*** SCAN CODE RANGE CHECKING ***
	;********************************

_KBD_2	;*** check if special code (0xE0) has been submitted previously ***
	btfss	SPEflag
	goto	_KBD_3
	;*** decoding of scan code with preceding special code (0xE0) ***
	; check for ALT-DEC or CTRL-HEX activity
	btfsc	ALTflag
	goto	_KBD_3
	btfsc	CTRLflag
	goto	_KBD_3
	; (decoding currently only necessary for 'E0 4A' = '/')
	movfw	KBD
	sublw	0x4A		; 0x4A - w
	bnz	_NOSUP		; branch on non-zero
	movlw	'/'		; store '/' in KBD
	movwf	KBD
	goto	_OUTP
_NOSUP	;*** check if scan code 0x5A or smaller has occurred ***
	movfw	KBD
	sublw	0x5A		; 0x5A - w
	bc	_KBD_3		; carry if result positive or zero, branch
	;*** range exceeded (above 0x5A) ***
	; it's one of the following keys: arrow button, 'Home', 'Del',
	; 'PageUp', 'PageDown', 'Insert', 'End'
	; these keys are currently not used, so
	goto	_ClrStall
_KBD_3	;*** check if scan code 0x61 or smaller has occurred ***
	movfw	KBD
	sublw	0x61		; 0x61 - w
	bc	KBD_dec		; carry if result positive or zero, goto table
	movlw	d'14'
	subwf	KBD,F		; KBD = KBD - d'14'
	;*** check if scan code 0x61 (0x6F-d'14') or smaller has occurred ***
	movfw	KBD
	sublw	0x61		; 0x61 - w
	bnc	_KBD_4		; no carry if result negative, goto _KBD_4
	movlw	d'25'
	addwf	KBD,F		; KBD = KBD + d'25'
	goto	KBD_dec
_KBD_4	;*** check if scan code 0x78 (0x86 - d'14') or higher has occurred ***
	movfw	KBD
	sublw	0x77		; 0x77 - w
	bc	KBD_dec		; carry if result zero or positive, branch
	;*** no character to display: ***
	;*** check for special code (0xE0): 0xD2 = 0xE0 - d'14' ***
	movfw	KBD
	sublw	0xD2		; 0xD2 - w
	skpnz			; skip if not zero
	bsf	SPEflag		; special code occurred, set flag
	goto	_ClrStall	; abort with nothing to display

	;*******************************************************
	;*** SCAN CODE DECODING & ASCII CHARACTER CONVERSION ***
	;*******************************************************

	;*** DECODE SCAN CODE ***
KBD_dec	movlw	HIGH KBDtable	; get correct page for PCLATH
	movwf	PCLATH		; prepare right page bits for table read
	movfw	KBD
	call	KBDtable	; GET CORRESPONDING KEYBOARD CHAR IN LOOKUP TABLE
	movwf	KBD		; save result to KBD
	tstf	KBD		; test KBD to get the zero flag
	bz	_ClrStall	; abort if KBD is zero / invalid entry, nothing to display

	;*** check for ALT-DEC-Entry ***
	btfsc	ALTflag		; check flag
	goto	_ALTstr		; jump if set

	;*** check for CTRL-HEX-Entry ***
	btfsc	CTRLflag	; check flag
	goto	_CTRLstr	; jump if set

	;*** convert only LETTERS to capital letters: ***
	; a letter in KBD value has to be in range from 0x61 to 0x7A
	movfw	KBD
	sublw	0x60		; 0x60 - w
	bc	_SHICHR		; carry if result greater or equal zero = no letter, exit
	movfw	KBD
	sublw	0x7A		; 0x7A - w
	bnc	_SHICHR		; no carry if result negative = no letter, exit
	;*** there is now a letter in KBD, check conversion to capital letter: ***
	movfw	KBD
	btfsc	CAPflag		; check caps lock flag
	goto	_SHIset		; flag has been set
	btfss	SHIflag		; check shift flag
	goto	_OUTP		; no flag, exit
	goto	_cnvCAP		; flag has been set, convert to capital letter
_SHIset	btfsc	SHIflag		; check shift flag
	goto	_OUTP		; flag has been set, exit
_cnvCAP	addlw	d'224'		; convert to capital letter (+ d'224')
	movwf	KBD
	;goto	_OUTP		; (uncomment in case _OUTP will be moved)

	;***************************************************
	;*** KEYBOARD DATA OUTPUT TO RS232 & LCD DISPLAY ***
	;***************************************************

_OUTP	btfsc	CUST_fl		; if set, user-customization in progress, branch
	goto	USER_CUST2
	;*** RS232 ***
	movfw	KBD
	SENDw			; send actual pressed keyboard character
	;*** LCD: treat 'backspace' as special case ***
	movfw	KBD		; check for backspace (d'8')
	BNEQ	d'8', _TAB	; branch if KBD != 8
	;*** it is now a 'backspace' ***
	movfw	LCDpos2		; load actual LCD cursor position
	bz	_ClrStall	; if zero, do nothing & exit
	decf	LCDpos2,F	; decrement LCD cursor position
	movfw	LCDpos2		; load cursor position to w
	addlw	0x40		; add offset (LCD line 2)
	iorlw	b'10000000'	; mask
	movwf	TEMP6		; store LCD DDRAM address pattern
	call	LCDcomd		; call LCD command subroutine 
	LCDchar	' '		; overwrite displayed character with blank
	movfw	TEMP6		; read back LCD DDRAM address pattern
	call	LCDcomd		; call LCD command subroutine
	goto	_ClrStall	; exit
_TAB	;*** LCD: treat 'tabulator' as special case ***
	movfw	KBD		; check for tabulator (d'9')
	BNEQ	d'9', _chkLn	; branch if KBD != 9
	;*** it is now a 'tabulator', just add one extra space ***
	movfw	LCDpos2		; get LCD position counter
	BREG	LCDwidth-0x1, _TAB2 ; check for 'near EOL'
	LCDchar	' '		; add extra space, if not near EOL
	incf	LCDpos2,F	; and increment LCD position counter
_TAB2	movlw	a' '
	movwf	KBD		; store second space in KBD
_chkLn	call LCDout_line2	; shared LCD output subroutine
	goto 	_ClrStall	; exit

USER_CUST2 ;*** user-customization of RS232 settings (baud rate setup) ***
	movfw	KBD		; retrieve value
	BNEQ	a'a', _CUST2_SET ; if no 'a', branch to next check
	call	_CUST1_CHANGE	; call common 'change' sub-routine
	IF BEEP_ENABLE == 1	; conditional assembly
	  BEEP	d'240', d'4'	; acoustic feedback
	ENDIF
	goto	_CUST2_END	; now terminate
_CUST2_SET
	movfw	KBD
	BNEQ	a's', _CUST2_END ; if no 's', branch to exit
	bcf	CUST_fl		; clear flag to indicate setup is finished
	IF BEEP_ENABLE == 1	; conditional assembly
	  BEEP	d'240', d'4'	; acoustic feedback
	ENDIF
_CUST2_END ; termination
	movlw	WDT_CNT_value	; load watchdog counter value
	movwf	WDT_CNT		; reset self-defined watchdog (customization time-out)
	goto	_ClrStall	; exit

	;************************************************
	;*** SPECIAL COMMANDS I (with special output) ***
	;************************************************

_CRLF	btfsc	CUST_fl		; if set, user-customization in progress, branch
	RETLW	0		; clear w to obtain invalid entry
	bsf	LCD_ln		; line flag = 1 (LCD line 2)
	call	LCDclrline	; call LCD clear line subroutine
	SEND	CR		; on "Enter", send CR and LF to RS232
	SEND	LF
	RETLW	0		; clear w to obtain invalid entry

	;**********************************************************
	;*** STALL RELEASE & CLEAR KEYBOARD DATA RECEPTION FLAG ***
	;**********************************************************
_ClrStall 
	BANK1
	bsf	KBDclktris	; set clk line back to input (and goes high)
	BANK0			;	(release stall)
	bcf	KBDflag		; clear keyboard data reception flag
	bsf	INTCON,INTE	; re-enable interrupt RB0/INT
	RETURN

	;****************************************************
	;*** SPECIAL COMMANDS II (without special output) ***
	;****************************************************

_SHIFT	bsf	SHIflag		; set shift flag
	RETLW	0		; clear w to obtain invalid entry

_ALT	bsf	ALTflag		; set ALT flag
	goto	_alt_2
_CTRL	bsf	CTRLflag	; set CTRL flag
_alt_2	clrf	CTRLcnt		; clear counter for CTRL/ALT conversion stuff
	clrf	CTRLreg1	; clear storage registers for CTRL-HEX
	clrf	CTRLreg2	; and ALT-DEC conversion routines
	clrf	CTRLreg3
	RETLW	0		; clear w to obtain invalid entry

	;***********************************************
	;*** ALT-DEC & CTRL-HEX STORING & CONVERSION ***
	;***********************************************
	; store typed numbers in CTRLreg1 - CTRLreg3
_CTRLstr ; check for capital letter [A..F], i.e. KBD in [0x41..0x46]
	movfw	KBD
	sublw	0x40		; 0x40 - w
	bc	_ALTstr		; carry if result >= 0, go to number check
	movfw	KBD
	sublw	0x46		; 0x46 - w
	bnc	_CTRLstr2	; no carry if result negative, go to next check
	; valid capital letter [A..F], convert to lower case (+ 0x20)
	bsf	KBD,0x5		; KBD += 0x20
 	goto	_CTRLstr3	; jump for storing
_CTRLstr2 ; check for lower case letter [a..f], i.e. KBD in [0x61..0x66]
	movfw	KBD
	sublw	0x60		; 0x60 - w
	bc	_ALTstr		; carry if result >= 0, go to number check
	movfw	KBD
	sublw	0x66		; 0x66 - w
	bc	_CTRLstr3	; carry if result >= 0, valid lower case letter [a..f]
_ALTstr	;*** check for number, i.e. KBD in [0x30..0x39]: ***
	movfw	KBD
	sublw	0x29		; 0x29 - w
	bc	_ClrStall	; carry if result >= 0, no number, exit
	movfw	KBD
	sublw	0x39		; 0x39 - w
	bnc	_ClrStall	; no carry if result negative, no number, exit
_CTRLstr3 ;*** store letter/number now ***
	tstf	CTRLcnt
	bnz	_cnt_1		; branch if not zero
	incf	CTRLcnt,F	; increment counter (0->1)
	movfw	KBD
	movwf	CTRLreg1	; store first number
	goto	_ClrStall	; abort & exit
_cnt_1	decfsz	CTRLcnt,W	; decrement counter, don't store
	goto	_cnt_2		; counter > 1
	incf	CTRLcnt,F	; increment counter (1->2)
	movfw	KBD
	movwf	CTRLreg2	; store second number
	goto	_ClrStall	; abort & exit
_cnt_2	movfw	CTRLcnt
	sublw	0x02		; 0x02 - w
	bnz	_ClrStall	; if result not zero: overflow, abort & exit
	incf	CTRLcnt,F	; increment counter (2->3)
	movfw	KBD
	movwf	CTRLreg3	; store third number
	goto	_ClrStall	; abort & exit

_ALTdec	; PRE: ALT + [1..3] numbers (e.g. ALT + 6 + 4 = @) in CTRLreg1 - 3
	; POST: convert ALT-DEC-Entry after release of ALT key, return
	;       ascii value converted from numbers
	tstf	CTRLcnt		; check, if counter has been incremented
	bz	_ClrStall	; if not, abort & exit
	movlw	0x30
	subwf	CTRLreg1,F	; CTRLreg1 = CTRLreg1 - 0x30
	subwf	CTRLreg2,F	; CTRLreg2 = CTRLreg2 - 0x30
	subwf	CTRLreg3,F	; CTRLreg3 = CTRLreg3 - 0x30
	;*** check if counter == 1 ***
	decf	CTRLcnt,F	; decrement counter
	bnz	_altd_1		; branch if not zero
	movfw	CTRLreg1	; get the only stored value
	movwf	KBD		; store in output register
	goto	_altd_3		; jump
_altd_1	;*** check if counter == 2 ***
	decf	CTRLcnt,F	; decrement counter
	bnz	_altd_2		; branch if not zero
	;*** check ok, convert now ***
	movlw	HIGH BCDtable	; get correct page for PCLATH
	movwf	PCLATH		; prepare right page bits for table read
	;*** 10's in CTRLreg1, 1's in CTRLreg2 ***
	movfw	CTRLreg1	; get the first stored value (10's)
	call	BCDtable	; BCD2BIN conversion through lookup table
	addwf	CTRLreg2,W	; add value in W to reg 2, store in W
	movwf	KBD		; store in output register
	goto	_altd_3		; jump
_altd_2	;*** else counter >= 3 ***
	;*** 100's in CTRLreg1, 10's in CTRLreg2, 1's in CTRLreg3 ***
	;*** range check: CTRLreg1 <= 2 ***
	movfw	CTRLreg1	; get the first stored value (100's)
	sublw	0x02		; 0x02 - w
	bnc	_ClrStall	; no carry if result negative, abort & exit
	;*** check ok, convert now ***
	movlw	HIGH BCDtable	; get correct page for PCLATH
	movwf	PCLATH		; prepare right page bits for table read
	movfw	CTRLreg1	; get the first stored value (100's)
	addlw	d'10'		; add offset for lookup table
	call	BCDtable	; BCD2BIN conversion through lookup table
	movwf	CTRLreg1	; store new value back to register
	movfw	CTRLreg2	; get the second stored value (10's)
	call	BCDtable	; BCD2BIN conversion through lookup table
	addwf	CTRLreg1,F	; add value in W to reg 1, and store
	movfw	CTRLreg3	; get the third stored value (1's)
	addwf	CTRLreg1,W	; add both registers, store in W
	movwf	KBD		; store in output register
_altd_3	clrf	CTRLcnt		; invalidate counter
	goto	_OUTP		; output

	;*******************************************************
	;* The following table has also to be located within   *
	;* one page to allow for correct lookup functionality. *
	;*******************************************************

	;ORG	0x??

BCDtable ; lookup table for BCD2BIN conversion
	addwf	PCL,F
	retlw	d'0'	; 0    BCD for 10's
	retlw	d'10'	; 10
	retlw	d'20'	; 20
	retlw	d'30'	; 30
	retlw	d'40'	; 40
	retlw	d'50'	; 50
	retlw	d'60'	; 60
	retlw	d'70'	; 70
	retlw	d'80'	; 80
	retlw	d'90'	; 90
	retlw	d'0'	; 0    BCD for 100's
	retlw	d'100'	; 100
BCDtableEND retlw d'200' ; 200

	IF (HIGH (BCDtable) != HIGH (BCDtableEND))
		ERROR "Lookup table for BCD2BIN conversion hits page boundary !"
	ENDIF


_CTRLhex ; PRE: CTRL + [1..2] letters/numbers (e.g. CTRL + 3 + F = ?)
	;       in CTRLreg1 - 2
	; POST: convert ALT-DEC-Entry after release of ALT key, return
	;       ascii value as concatenated hex value from numbers
	tstf	CTRLcnt		; check, if counter has been incremented
	bz	_ClrStall	; if not, abort & exit
	movlw	0x30
	subwf	CTRLreg1,F	; CTRLreg1 = CTRLreg1 - 0x30
	subwf	CTRLreg2,F	; CTRLreg2 = CTRLreg2 - 0x30
	; if CTRLregs have numbers, now in [1..9]
	; if CTRLregs have letters [a..f], now in [0x31..0x36]
	; (no plausiblity checks necessary since done during storing)
	;*** first register ***
	btfss	CTRLreg1,0x5	; in [0x31..0x36], bit 5 is set
	goto	_ctrh_1		; it's a number, branch to next check
	; it's a letter: subtract offset, result afterwards in [d'10'..d'15']
	movlw	d'39'
	subwf	CTRLreg1,F	; CTRLreg1 = CTRLreg1 - W
_ctrh_1	;*** second register ***
	btfss	CTRLreg2,0x5	; in [0x31..0x36], bit 5 is set
	goto	_ctrh_2		; it's a number, branch
	; it's a letter: subtract offset, result afterwards in [d'10'..d'15']
	movlw	d'39'
	subwf	CTRLreg2,F	; CTRLreg2 = CTRLreg2 - W
_ctrh_2	;*** check if counter == 1 ***
	decf	CTRLcnt,F	; decrement counter
	bnz	_ctrh_3		; branch if not zero
	movfw	CTRLreg1	; get the only stored value
	movwf	KBD		; store in output register
	goto	_ctrh_4		; jump
_ctrh_3	;*** else counter >= 2, conversion ***
	swapf	CTRLreg1,W	; swap nibbles, store in W
	iorwf	CTRLreg2,W	; merge both registers, store in W
	movwf	KBD		; store in output register
_ctrh_4	clrf	CTRLcnt		; invalidate counter
	goto	_OUTP		; output

	;*****************************************************************
	;*** SCAN CODE DECODING & ASCII CONVERSION FOR 'SHIFT' ENTRIES ***
	;*****************************************************************

_SHICHR	;*** special character decoding typed with shift button active ***
	; check for active shift button, if not active, branch
	btfss	SHIflag
	goto	_OUTP		; branch
	; check for 'backspace', 'tab', 'linefeed' and 'carriage return' :
	; (here, KBD has already been converted to ASCII character values)
	movfw	KBD
	sublw	d'13'		; d'13' - w
	bc	_OUTP		; carry if result zero or positive, branch

	;*** range check: abort if KBDcopy greater than 0x61 ***
	; (KBDcopy has the value of the original keyboard scan code)
	movfw	KBDcopy
	sublw	0x61		; 0x61 - w
	bnc	_OUTP		; no carry if result negative, branch
	;*** check if KBDcopy greater than 0x3C ***
	movfw	KBDcopy
	sublw	0x3C		; 0x3C - w
	bc	_SHICH1		; carry if result zero or positive, branch
	movlw	d'61'
	subwf	KBDcopy,F	; KBDcopy = KBDcopy - d'61'
	goto	_SHICH3
	;*** check if KBDcopy greater than 0x24 ***
_SHICH1	movfw	KBDcopy
	sublw	0x24		; 0x24 - w
	bc	_SHICH2		; carry if result zero or positive, branch
	movlw	d'35'
	subwf	KBDcopy,F	; KBDcopy = KBDcopy - d'35'
	goto	_SHICH3
	;*** else ***
_SHICH2	movlw	d'4'
	addwf	KBDcopy,F	; KBDcopy = KBDcopy + d'4'

_SHICH3	movlw	HIGH KBDSHIFTtable ; get correct page for PCLATH
	movwf	PCLATH		; prepare right page bits for table read
	movfw	KBDcopy
	call	KBDSHIFTtable	; GET CORRESPONDING KEYBOARD CHAR IN LOOKUP TABLE
	movwf	KBD		; save result to KBD
	goto	_OUTP

	;*******************************************************
	;* The following table has also to be located within   *
	;* one page to allow for correct lookup functionality. *
	;*******************************************************

	;**********************************************************************
	;* LOOKUP TABLE FOR SPECIAL CHARACTERS TYPED WITH SHIFT BUTTON ACTIVE *
	;**********************************************************************

	;ORG	0x??			; if necessary, move table

	#include "..\tables\eng_shif.asm"  	; English 'codepage'
	;#include "..\tables\ger_shif.asm"  	; modified Swiss-German 'codepage'

;***** END OF SCAN PATTERN DECODING SUBROUTINE *****


;************** MAIN **************

MAIN	ORG	0x00

	BANK1
	clrf	OPTION_REG	; PORTB pull-ups enabled
	goto	_MAIN

	ORG	0x100

_MAIN	;*** configure port A ***
	; Note: Initially, I have spent approx. 25 hours on debugging
	; this AT keyboard interface in conjunction with the PIC 16C74A,
	; because the port A inputs are set to analog inputs by default !!!

	; Configure RA0, RA1, RA2, RA3, RA5 to analog inputs,
	; but leave RA4, RE0, RE1, RE2 as digital I/O ports.
	; Analog reference voltage is VDD.
	movlw	b'00000010'
	movwf	ADCON1

;	bcf	DEBUG0tris		; DEBUGGING !!!
;	bcf	DEBUG1tris
;	bcf	DEBUG2tris
;	bcf	DEBUG3tris
;	bcf	DEBUG4tris
;	bcf	DEBUG5tris
;	bcf	DEBUG6tris
;	bcf	DEBUG7tris

	BANK0

;	bcf	DEBUG0
;	bcf	DEBUG1
;	bcf	DEBUG2
;	bcf	DEBUG3
;	bcf	DEBUG4
;	bcf	DEBUG5
;	bcf	DEBUG6
;	bcf	DEBUG7

	;*** PIEZO BEEPER INITIALIZATION ***
	IF BEEP_ENABLE == 1	; conditional assembly
	  BEEPinit		; beeper initialization
	ENDIF

	;*** AT KEYBOARD INITIALIZATION I ***
	KEYBOARDinit		; keyboard initialization

	;*** NUMERIC KEYPAD INITIALIZATION ***
	KEYPADinit		; keypad initialization

	;*** ENABLE ALL INTERRUPTS ***
	movlw	b'11111000'
	andwf	INTCON,F	; clear all interrupt flags
	clrf	PIR1		; clear all interrupt flags
	clrf	PIR2		; clear all interrupt flags
	bsf	INTCON,GIE	; enable global interrupt

	;*** AT KEYBOARD INITIALIZATION II ***
	movlw	b'00000010'
	movwf	KBDleds		; init keyboard LEDs' status register
	clrf	KBDcnt		; clear IRQ based scan pattern counter
	clrf	FLAGreg		; clear all flags (keyboard & others)
	clrf	FLAGreg2
	clrf	FLAGreg3

	;*** LCD INITIALIZATION ***
	LCDinit			; LCD display initialization
	movlw	LCDwidth
	movwf	LCDpos1		; init LCD output position counters
	clrf	LCDpos2

	;*** CUSTOMIZATION MESSAGE ***
	#define	tab_size1 d'38'	; define amount of table items to display
	movlw	tab_size1	; store amount of table items in counter
	movwf	TEMP6
	; LCD start-up/customization message
_ILOOP	movlw	HIGH StartupTable ; get correct page for PCLATH
	movwf	PCLATH		; prepare right page bits for table read
	movfw	TEMP6		; get actual count-down value
	sublw	tab_size1	; table offset: w = tab_size1 - TEMP6
	call	StartupTable	; call lookup table
	LCDw			; LCD output
	decfsz	TEMP6,F		; decrement counter
	goto	_ILOOP		; loop again

	;*** AT KEYBOARD INITIALIZATION III ***
	;*** reset keyboard ***
	;bsf	DEBUG0		; ### DEBUGGING ###
	KBDcmd	0xFF		; reset keyboard
	;bsf	DEBUG1		; ### DEBUGGING ###
	;KBDexp	0xFA		; expect 0xFA => issue with some keyboards
	;*** switch keyboard LEDs on (default status) ***
	KBDcmd	0xED		; keyboard LEDs' control command
	KBDexp	0xFA		; expect keyboard acknowledge (FA)
	movfw	KBDleds		; load keyboard LEDs' status
	KBDcmdw			; send keyboard LEDs' control data
	KBDexp	0xFA		; expect keyboard acknowledge (FA)
	;*** switch scroll lock LED on (example) ***
	KBDcmd	0xED		; keyboard LEDs' control command
	KBDexp	0xFA		; expect keyboard acknowledge (FA)
	bsf	KBDleds,SL	; set scroll lock bit
	movfw	KBDleds		; load keyboard LEDs' status
	KBDcmdw			; send keyboard LEDs' control data
	KBDexp	0xFA		; expect keyboard acknowledge (FA)

	IF BEEP_ENABLE == 1	; conditional assembly
	  ;*** acoustic introduction ***
	  BEEP	d'255', d'4'	; "device is ready"
	  WAIT	0x80
	  BEEP	d'240', d'4'
	ENDIF

	;*** RS232 USER CUSTOMIZATION & INITIALIZATION ***
	LCD_DDAdr 0x40
	#define	tab_size2 d'40'	; define amount of table items to display
	movlw	tab_size2	; store amount of table items in counter
	movwf	TEMP6
	; transmit startup message
_ILOOP2	movlw	HIGH RS_Table1	; get correct page for PCLATH
	movwf	PCLATH		; prepare right page bits for table read
	movfw	TEMP6		; get actual count-down value
	sublw	tab_size2	; table offset: w = tab_size2 - TEMP6
	call	RS_Table1	; call lookup table
	LCDw			; LCD output
	decfsz	TEMP6,F		; decrement counter
	goto	_ILOOP2		; loop again

	bsf	CUST_fl		; enable customization flag
	movlw	d'23'		; default: 9600 baud @ 14.7456 MHz
	movwf	RS232_CUST	; store default baud rate value
	movlw	d'3'		; default
	movwf	CUST_CNT	; counter for customization loop

	; set up watchdog timer for user-customization (12 seconds)
	; global interrupt is already enabled
	movlw	WDT_CNT_value	; load watchdog counter value
	call	WDT_SETUP	; call sub-routine for IRQ-based WDT timer

	;******************************
_ILOOP3	btfsc	KPflag		; check foil-keypad reception flag
	call	KEYPADout	; if set, call output routine
	btfsc	KBDflag		; check scan pattern reception flag
	call	KBDdecode	; if set, call decoding routine
	btfsc	CUST_fl		; if customization flag is cleared, exit loop
	goto	_ILOOP3		; loop again
	;******************************

	; stop watchdog timer for user-customization
	call	WDT_STOP	; stop & reset watchdog

	IF BEEP_ENABLE == 1	; conditional assembly
	  WAIT	0x80
	  BEEP	d'240', d'4'	; acoustic feedback
	  WAIT	0x80
	  BEEP	d'240', d'4'
	ENDIF

	; RS232 initialization
	bcf	INTCON,GIE	; enable global interrupt
	movfw	RS232_CUST	; retrieve customized baud rate value
	RS232init		; RS232 initialization
	movlw	b'11111000'
	andwf	INTCON,F	; clear all interrupt flags
	clrf	PIR1		; clear all interrupt flags
	clrf	PIR2		; clear all interrupt flags
	bsf	INTCON,GIE	; enable global interrupt

	LCDcmd	LCDCLR		; clear entire LCD display

	;*** START-UP MESSAGE ***
	; define amount of table items for start-up message
	#define	tab_size3 d'39'
	movlw	tab_size3	; store amount of table items in counter
	movwf	TEMP6
	; transmit message
_ILOOP4	movlw	HIGH WelcomeTable ; get correct page for PCLATH
	movwf	PCLATH		; prepare right page bits for table read
	movfw	TEMP6		; get actual count-down value
	sublw	tab_size3	; table offset: w = tab_size3 - TEMP6
	call	WelcomeTable	; call lookup table
	movwf	TEMP7		; create backup of fetched item
	SENDw			; RS232 output
	movfw	TEMP7
	LCDw			; LCD output
	decfsz	TEMP6,F		; decrement counter
	goto	_ILOOP4
	SEND	CR		; carriage return
	SEND	LF		; line feed
	SEND	LF

	;******************************
_MLOOP	btfsc	KBDflag		; check scan pattern reception flag
	call	KBDdecode	; if set, call decoding routine
	btfsc	RSflag		; check RS232 data reception flag
	call	RSdisplay	; if set, call display routine
	btfsc	KPflag		; check foil-keypad reception flag
	call	KEYPADout	; if set, call output routine
	goto	_MLOOP		; loop forever
	;******************************

	ORG	0x600		; if necessary, move look-up tables

StartupTable
	addwf	PCL,F		; add offset to table base pointer
	DT	"Customization of Serial Communication" ; create table
StartupTableEND DT ":"
	IF (HIGH (StartupTable) != HIGH (StartupTableEND))
		ERROR "StartupTable hits page boundary!"
	ENDIF

WelcomeTable
	addwf	PCL,F		; add offset to table base pointer
	DT	"PIC16F77 AT Keyboard Box V2.05 stand-b" ; create table
WTableEND DT "y"
	IF (HIGH (WelcomeTable) != HIGH (WTableEND))
		ERROR "WelcomeTable hits page boundary!"
	ENDIF

RS_Table1
	addwf	PCL,F		; add offset to table base pointer
	DT	"RS232   9600 baud  a/* alter  s/# selec" ; create table
RS_Table1END DT "t"
	IF (HIGH (RS_Table1) != HIGH (RS_Table1END))
		ERROR "RS_Table1 hits page boundary!"
	ENDIF

CUST_Table1
	addwf	PCL,F		; add offset to table base pointer
	DT	"  12"		; create table
	DT	"  24"
	DT	"  48"
	DT	"  96"
	DT	" 192"
	DT	" 384"
	DT	" 576"
	DT	"115"
CUST_Table1END DT "2"
	IF (HIGH (CUST_Table1) != HIGH (CUST_Table1END))
		ERROR "CUST_Table1 hits page boundary!"
	ENDIF

CUST_Table2
	addwf	PCL,F		; add offset to table base pointer
	retlw	d'191'		; 1200 baud
	retlw	d'95'		; 2400 baud
	retlw	d'47'		; 4800 baud
	retlw	d'23'		; 9600 baud
	retlw	d'11'		; 19200 baud
	retlw	d'5'		; 38400 baud
	retlw	d'3'		; 57600 baud
CUST_Table2END 	retlw d'1'	; 115200 baud
	IF (HIGH (CUST_Table2) != HIGH (CUST_Table2END))
		ERROR "CUST_Table2 hits page boundary!"
	ENDIF

KeypadTable1
	addwf	PCL,F		; add offset to table base pointer
	retlw	d'76'		; *
	retlw	d'90'		; 7
	retlw	d'105'		; 4
	retlw	d'120'		; 1
	retlw	d'135'		; 0
	retlw	d'151'		; 8
	retlw	d'167'		; 5
	retlw	d'184'		; 2
	retlw	d'201'		; #
	retlw	d'218'		; 9
	retlw	d'236'		; 6
KeypadTable1END	retlw	d'254'	; 3
	IF (HIGH (KeypadTable1) != HIGH (KeypadTable1END))
		ERROR "KeypadTable1 hits page boundary!"
	ENDIF

KeypadTable2
	addwf	PCL,F		; add offset to table base pointer
	DT	"*7410852#96"	; create table
KeypadTable2END	DT "3"
	IF (HIGH (KeypadTable2) != HIGH (KeypadTable2END))
		ERROR "KeypadTable2 hits page boundary!"
	ENDIF

	END


; A/D conversion values for numeric foil-keypad using different clocks:
; =====================================================================
; unbounce keypad IRQ and keypad analog signals using 5 nF capacitors
; symbol   14.7456 MHz    4 MHz
; *            76           76 
; 7            90           91 
; 4           105          106
; 1           120          121
; 0           135          137
; 8           151          152
; 5           167          168
; 2           184          185
; #           201          201
; 9           218          218
; 6           236          236
; 3           254          254

