Newer
Older
pic-keypad / keypad.asm
;;; 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