;;; KEYPAD.ASM ;;; Copyright (c) 2002, TundraWare Inc., All Rights Reserved ;;; ;;; Program to encode keypad presses into binary numbers ;;; ;;; $Id: keypad.asm,v 1.34 2002/06/18 16:44:33 tundra Exp tundra $ ;;;;;;;;;; ;;; This code assumes the following keypad wiring to PORTB on the PIC: ;;; ;;; RB7 RB6 RB5 RB4 RB3 RB2 RB1 RB0 ;;; Col. Col. Col. Col. Row Row Row Row ;;; 4 3 2 1 4 3 2 1 ;;;;;;;;;; list p=16F84A include <P16F84A.INC> errorlevel -302 ;suppress bank selection messages __config 3ff3h ;RC osc, WDT off __idlocs 1234 ;;;;;;;;;; ;;; Device Constants ;;;;;;;;;; tmr2 equ 00H ; TMR0 prescaler constants tmr4 equ 01H tmr8 equ 02H tmr16 equ 03H tmr32 equ 04H tmr64 equ 05H tmr128 equ 06H tmr256 equ 07H ;;;;;;;;;; ;;; Circuit Constants ;;;;;;;;;; KEYPAD equ PORTB OUTPUT equ PORTA PAR_SEL equ 4 ; bit used to select parallel/serial output PLATCH equ 4 ; parallel latch bit SCLK equ 1 ; serial clock out bit SDAT equ 0 ; serial data out bit SLATCH equ 2 ; serial latch bit TRISKEY equ TRISB TRISOUT equ TRISA ; Constants used to Set TMR0 interrupt rate. ; Adjust as needed using the following formula: ; ; I = T * TMR0_CNT * Prescaler Factor ; ; TMR0_PRIME is the value actually used to prime ; TMR0 when restarting it - it counts *up* and ; interrupts on overflow. ; ; The following TMR0 constants are approximately: ; ; (T) Internal Clock Interval: 3us ; (I) TMR0 Interrupt Rate: 480us TMR0_CNT equ D'5' TMR0_PRE equ tmr32 TMR0_PRIME equ D'256'-TMR0_CNT ;;;;;;;;;; ;;; Program Contants ;;;;;;;;;; DEBOUNCE_COUNT equ 05h ; # times keyhit must be unchanged to be valid DEBOUNCE_TIME equ 0ah ; # TMR0 interrupts to wait between keyboard reads CLK_TIME equ 01h ; # TMR0 interrupts for serial clock true MAX_COL equ 04h ; # of columns on the keyboard NO_HIT equ 0fh ; bit pattern which means no key has been hit LATCH_TIME equ 01h ; # TMR0 interrupts to leave latch line true REPEAT_COUNT equ 03h ; scan delay = ; REPEAT_COUNT * REPEAT_DELAY * TMR0 rate REPEAT_DELAY equ D'87' ;;;;;;;;;; ;;; Register File Assignments ;;;;;;;;;; CBLOCK 0cH ; context save area _fsr _pclath _status _w ; flags par_out ; non-zero -> parallel out; zero -> serial out ; variables count current_col key_val wait_count ENDC ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;; ;;; Power-On Entry Point ;;;;;;;;;; org 00H goto main ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;; ;;; Interrupt Handler ;;;;;;;;;; org 04H movwf _w ; save context movfw STATUS bcf STATUS,RP1 bcf STATUS,RP0 movwf _status movfw FSR movwf _fsr movfw PCLATH movwf _pclath clrf PCLATH ; Figure out who interrupted and service it btfsc INTCON,T0IF ; did we get a TMR0 interrupt? goto tmr0_svc ; yup goto restore_context ; no supported interrupt type found ; TMR0 ISR tmr0_svc: bcf INTCON,T0IF ; clear the interrupt condition movlw TMR0_PRIME ; prime the counter movwf TMR0 ; Update the wait timer if it is in use ; Only do this on TMR0 interrupts so timing is predictable movfw wait_count btfss STATUS,Z ; non-zero count means 'in use' decf wait_count,F ; decrement current count ;;; End TMR0 ISR restore_context: movfw _pclath ; restore entry context movwf PCLATH movfw _fsr movwf FSR movfw _status movwf STATUS swapf _w,F ; get W back w/o changing Z flag swapf _w,W retfie ; all done ;;; End of interrupt handler ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;; ;;; Main program logic ;;;;;;;;;; main: call init ; initialize the system scan: incf current_col,F ; pickup next column to scan movlw MAX_COL+1 ; if current_col > MAX_COL then subwf current_col,W ; we just did last column, so start over btfss STATUS,Z ; zero means we need to start over goto key_scan ; nope, go look for key hits clrf current_col ; yes, reinit column counter goto scan key_scan: movfw current_col ; get bit pattern which call col_select ; selects curently desired column movwf KEYPAD ; and enable that column movfw KEYPAD ; get keypad inputs andlw 0fh ; only want row bits addlw -NO_HIT ; see if key hit occured btfsc STATUS,Z ; in current column goto scan ; no, look at next column call key_get ; yes, process ignore key releaseskeystroke andlw 0fh ; there is bounce on a key *release* addlw -NO_HIT ; as well as a key *press* btfsc STATUS,Z ; we want to debounce key releases goto scan ; (so they do not look like legit keystrokes) ; but then we just ignore the release call key_xlate ; legit keystroke - turn into binary value call key_out ; and output it ; introduce wait between keyboard scans to limit ; key repeat rate movlw REPEAT_COUNT movwf count scan_delay: movlw REPEAT_DELAY call wait decfsz count,F goto scan_delay goto scan ; scan forever ;;; End of main logic ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;; ;;; Data & Tables ;;;;;;;;;; ;;;;;;;;;; ;;; Embedded Release & Copyright Information ;;;;;;;;;; DA "keypad.asm - $Revision: 1.34 $ - Copyright 2002, TundraWare Inc., All Rights Reserved." ; End of Embedded Release & Copyright Information ;;;;;;;;;; ;;; Column selection bit patterns. ;;;;;;;;;; col_select: addwf PCL,F DT b'11110000' ; placeholder, no column selected DT b'11100000' ; column 1 selected DT b'11010000' ; column 2 selected DT b'10110000' ; column 3 selected DT b'01110000' ; column 4 selected ;;;;;;;;;; ;;; Translate row/column selection pattern into equivalent binary value. ;;; There is one subtable for each column. '*' is translated of 0eh and ;;; '#' is translated to 0fh. This allows the entire keyboard to be ;;; uniquely translated to 1 of 16 hex output values. ;;;;;;;;;; col1_keys: addwf PCL,F DT 01h ; Row 1 DT 04h ; Row 2 DT 07h ; Row 3 DT 0eh ; Row 4 col2_keys: addwf PCL,F DT 02h ; Row 1 DT 05h ; Row 2 DT 08h ; Row 3 DT 00h ; Row 4 col3_keys: addwf PCL,F DT 03h ; Row 1 DT 06h ; Row 2 DT 09h ; Row 3 DT 0fh ; Row 4 col4_keys: addwf PCL,F DT 0ah ; Row 1 DT 0b ; Row 2 DT 0c ; Row 3 DT 0dh ; Row 4 ;;;;;;;;;; ;;; Translate a nibble bit pattern where an on bit means that row was selected ;;; into an equivalent binary value for that row. ;;;;;;;;;; bit2row: addwf PCL,F DT 00H ; placeholder, never used DT 00H ; b'0001' => row 1 DT 01H ; b'0010' => row 2 DT 00H ; placeholder, never used DT 02H ; b'0100' => row 3 DT 00H ; placeholder, never used DT 00H ; placeholder, never used DT 00H ; placeholder, never used DT 03H ; b'1000' => row 4 ;;; End of translation tables ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;; ;;; Supporting Subroutines ;;;;;;;;;; ;;;;;;;;;; ;;; Output current keystroke. ;;; This routine supports both serial and parallel output modes, ;;; which are selected at init time as follows: ;;; ;;; A4=0 ===> Serial Output ;;; A4=1 ===> Parallel Output ;;;;;;;;;; key_out: movlw 0 ; clear W addwf par_out,W ; if 0, we want serial output btfsc STATUS,Z goto serial_out ; parallel output routine movfw key_val ; load the value movwf OUTPUT bsf OUTPUT,PLATCH ; latch on movlw LATCH_TIME call wait bcf OUTPUT,PLATCH ; latch off return ; serial output routine serial_out: movlw 04h ; we have to shift out 4 bits LSB->MSB movwf count shift_bit: bcf OUTPUT,SDAT ; assume it's a 0 btfss key_val,0 ; see what it really is goto clock_bit bsf OUTPUT,SDAT ; nope, it was a 1 clock_bit: bsf OUTPUT,SCLK ; clock it out movlw CLK_TIME call wait bcf OUTPUT,SCLK ; reset clock bit decf count,F ; see if bits left to do btfsc STATUS,Z goto serial_latch ; all done rrf key_val,F ; pickup next bit goto shift_bit ; and move it ; now latch the data out serial_latch: bsf OUTPUT,SLATCH ; latch on movlw LATCH_TIME call wait bcf OUTPUT,SLATCH ; latch off return ;;; End of 'key_out' ;;;;;;;;;; ;;; Read a debounced key from the keyboard. ;;; Keep reading keyboard until the value has not changed ;;; DEBOUNCE_COUNT times. Wait an interval of DEBOUNCE_TIME ;;; between read attempts ;;;;;;;;;; key_get: movfw KEYPAD movwf key_val debounce: movlw DEBOUNCE_COUNT ; setup debounce counter movwf count check_key: movlw DEBOUNCE_TIME ; wait to let keyboard settle call wait movfw KEYPAD subwf key_val,F ; see if matched last btfsc STATUS,Z ; if Z, the values matched goto matched movwf key_val ; no match, start debounce cycle again goto debounce matched: movwf key_val ; save in case we have to do again decfsz count,F ; see how many times left to go goto check_key ; more matches to do return ; got DEBOUNCE_COUNT matches - all done ; End of 'key_get' ;;;;;;;;;; ;;; Translate the current keystroke into an equivalent binary value. ;;;;;;;;;; key_xlate: ; translate the hardware bit pattern indicating ; which row was selected into an equivalent binary ; row number. This will be used to index into ; the translation table for the current column to ; get the final binary value for that key. comf key_val,W ; complement so only bit on corresponds andlw 0fh ; to row selected - mask unused off call bit2row ; translate it into a row number movwf key_val ; save for use below ; translate key value into equivalent binary value ; using the appropriate translation table for the ; current column movfw current_col ; get current column addlw -1 ; convert into an 0-relative offset addwf PCL,F ; dispatch into the following jump table goto col1_xlate goto col2_xlate goto col3_xlate goto col4_xlate col1_xlate: movfw key_val call col1_keys movwf key_val return col2_xlate: movfw key_val call col2_keys movwf key_val return col3_xlate: movfw key_val call col3_keys movwf key_val return col4_xlate: movfw key_val call col4_keys movwf key_val return ;;; End of 'key_xlate' ;;;;;;;;;; ;;; Generic wait routine. ;;; Expects wait count to be passed in W. ;;; Total wait time is thus W * TMR0 interrupt rate ;;;;;;;;;; wait: movwf wait_count waiting: movfw wait_count ; wait until we countdown to 0 btfss STATUS,Z goto waiting return ;;; End Of 'wait' ;;;;;;;;;; ;;; Initialize hardware and software at startup as needed. ;;; Returns with register bank 0 selected. ;;;;;;;;;; init: clrf INTCON ; all interrupts off, flags cleared ; determine whether we want serial or parallel output ; and set flag accordingly for use later in the output routine clrf par_out ; assume serial btfsc OUTPUT,PAR_SEL ; if high - parallel out comf par_out,F ; we want parallel output ; Setup control registers bsf STATUS,RP0 ; select register bank 1 ; setup output port with all bits output enabled movlw b'11100000' ; set OUTPUT 0:4 as outputs movwf TRISOUT ; (to output binary value of keypress) ; Setup control registers bsf STATUS,RP0 ; select register bank 1 ; setup keypad interface port movlw b'00001111' ; KEYPAD high nibble set for output ; (for column control) movwf TRISKEY ; KEYPAD low nibble set for input ; (to read rows) ; setup TMR0 interrupt interval clrf OPTION_REG ; TMR0: assign int. clock & prescaler ; PORTB: weak pullups movlw TMR0_PRE ; Set TMR0 prescale factor iorwf OPTION_REG,F bcf STATUS,RP0 ; select register bank 0 ; Prime the TMR0 countup counter movlw TMR0_PRIME ; prime the counter movwf TMR0 ; Intialize ports clrf OUTPUT clrf KEYPAD ; Initialize variables clrf current_col clrf key_val clrf count clrf wait_count ; Setup interrupt handling bsf INTCON,T0IE ; enable TMR0 interrupt bsf INTCON,GIE ; and turn interrupts back on return ;;; End of 'init' ;;;;;;;;;; ;;; EEPROM Contents ;;;;;;;;;; org 2100h DE "" END