;*************************************************************************** ; ; 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