; This program implements a PS/2 keyboard interface for use with a Parallax BASIC Stamp II. It supports ; all standard PS/2 keyboard commands. It also provides a simple LCD interface. All communications ; with the Stamp are synchronous-serial and compatible with the Stamp's SHIFTIN and SHIFTOUT commands. ; The interface program in conjuntion with a keyboard, LCD and Stamp accomplishes typical keyboard ; and display functions for BASIC Stamp programs using only two Stamp pins. The interface program is ; implemented in a Scenix SX28 and uses the SX28's internal oscillator. ; Author: Steve Parkis. Pls direct comments, additional information, or corrections to ; parkiss@earthlink.net ; Basic Stamp is a registered trademark of Parallax, Inc. DEVICE SX28L, OSC4MHZ, TURBO, STACKX_OPTIONX ; "A" silicon, 4 MHz internal oscillator RESET startExecution ; begin execution location ;************************************************************************** ; Pin Definitions and Equates ;************************************************************************** ; SX28 port a equates (ra.0 through ra.3 connected to LCD d4 through d7) ; SX28 port b equates keyboard_clock_pin equ 0 ; connected to keyboard clock line keyboard_data_pin equ 1 ; connected to keyboard data line stamp_clock_pin equ 2 ; connected to Stamp's designated clock pin stamp_data_pin equ 3 ; connected to Stamp's designated data pin ; SX28 port c equates lcd_rs equ 4 ; LCD register select output lcd_en equ 5 ; LCD enable output lcd_rw equ 6 ; LCD read/write output ;************************************************************************** ; Equates ;************************************************************************** ; keyboard command equates set_LEDs equ $ed ; turn LED's on/off send_echo equ $ee ; echo $ee scan_code equ $f0 ; set or identify scan code id_keyboard equ $f2 ; send id info set_rates equ $f3 ; set auto-repeat, delay rates enable_keyboard equ $f4 ; enable transfers from keyboard disable_std equ $f5 ; set keyboard to standard values and disable transfers enable_std equ $f6 ; set keyboard to standard values and enable transfers keyboard_reset equ $ff ; reset, do self-test ; keyboard reply equate keyboard_ack equ $fa ; command acknowledged keyboard_huh equ $fe ; last command invalid--retransmit keyboard_bat equ $aa ; self-test completed successfully ; keyboard flag equates scroll_locked equ 0 ; bit set if scroll lock in effect num_locked equ 1 ; bit set if num lock in effect caps_locked equ 2 ; bit set if caps lock in effect shift_down equ 3 ; bit set if left/right shift key depressed alt_down equ 4 ; bit set if left/right alt key depressed ctrl_down equ 5 ; bit set if left/right ctrl key depressed win_down equ 6 ; bit set if left/right win key depressed (W95 kybd) valid_data equ 7 ; bit set if follow-on byte is real keyboard data ; boolean equates True equ 1 False equ 0 ; port control register equates CMPR equ $08 WKPND equ $09 WKED equ $0a WKEN equ $0b ST equ $0c LVL equ $0d PLP equ $0e DIR equ $0f ; register bank equates Zero equ $00 One equ $30 Two equ $50 Three equ $70 Four equ $90 Five equ $B0 Six equ $D0 Seven equ $F0 ; LCD equates lcd_busy equ 3 ; bit position (HO bit of nibble) external_reset equ TRUE ; controls whether external reset logic executed ;************************************************************************** ; Global Variables ;************************************************************************** org $8 lcd_out ds 1 ; command or data destined for LCD lcd_in ds 1 ; data from LCD tst ds 1 ; byte for binary display on LCD bit_counter ds 1 ; counts bits for debug binary display delay0 ds 1 ; delay variables delay1 ds 1 delay2 ds 1 byte_counter ds 1 ; counts bytes for character demo ;************************************************************************** ; Bank 0 (non-global) registers ;************************************************************************** b_pins ds 1 ; shadow copy of port b outputs b_dirs ds 1 ; shadow copy of port b data directions keyboard_data ds 1 ; byte to/from keyboard stamp_data ds 1 ; byte to/from Stamp loop_counter ds 1 temp ds 1 keyboard_flags ds 1 ; see equates above keyboard_0 ds 1 ; keyboard data holder ascii_data ds 1 ; result of scan code to ASCII conversion ;************************************************************************** ; Bank 1 (non-global) registers ;************************************************************************** org $30 ;************************************************************************** ; Macros ;************************************************************************** Input MACRO 1 ; make port b pin an input ("Input 1") setb b_dirs.\1 ; set pin's bit in port direction image mov !rb,b_dirs ; update DIR_B ENDM Output MACRO 1 ; make port b pin an output ("Outut 1") clrb b_dirs.\1 ; reset pin's bit in port direction image mov !rb,b_dirs ; update DIR_B ENDM High MACRO 1 ; make port b pin an output, high ("High 1") setb b_pins.\1 ; set pin's bit in port logic level image mov rb,b_pins ; update port clrb b_dirs.\1 ; reset pin's bit in port direction image mov !rb,b_dirs ; update DIR_B ENDM Low MACRO 1 ; make port b pin an output, low ("Low 1") clrb b_pins.\1 ; reset pin's bit in port logic level image mov rb,b_pins ; update port clrb b_dirs.\1 ; reset pin's bit in port direction image mov !rb,b_dirs ; update DIR_B ENDM jlo MACRO 2 ; jump if low ("jlo bytex.7,target") sb \1 jmp \2 ENDM jhi MACRO 2 ; jump if high ("jhi bytex.7,target") snb \1 jmp \2 ENDM cmp MACRO 2 ; compare register to literal ("cmp counter,8") mov w,#\2 mov w,\1 - w ENDM jge MACRO 1 ; jump if >= ("jge target" after cmp) jc \1 ENDM jlt MACRO 1 ; jump if < ("jlt target" after cmp) jnc \1 ENDM ;############################################################################### ##################### ;############################################################################### ##################### org $000 ; page 0, interrupt service routine & start ;############################################################################### ##################### ;############################################################################### ##################### ; (ISR not implemented) startExecution call @init ; initialize variables call @initLCD ; initialize LCD mainLoop call getStampByte ; get command from Stamp add pc,stamp_data ; use as offset into branch table jmp SXecho jmp resetKeyboard jmp dataToLCD jmp commandToLCD jmp getKeyboardID jmp ledSettings jmp scanCodeCheck jmp scanCodeSet jmp disableStd jmp enableStd jmp keyboardEnable jmp keyboardEcho jmp keySend jmp ratesSet jmp readLCDChar jmp setLCDAddress jmp reportLCDAddress jmp allReturn SXecho ; command code $00: echo following byte back to Stamp (local loopback) call getStampByte ; get byte from Stamp call putStampByte ; send it back to Stamp jmp mainLoop resetKeyboard ; command code $01: reset keyboard call @keyboardReset mov stamp_data,keyboard_data ; position self-test results for transmission call putStampByte ; send results back to Stamp jmp mainLoop dataToLCD ; command code $02: send byte to LCD as data call getStampByte ; get byte from Stamp mov lcd_out,stamp_data ; position for transmission to LCD call @lcdData ; send it as data jmp mainLoop commandToLCD ; command code $03: send byte to LCD as command call getStampByte ; get byte from Stamp mov lcd_out,stamp_data ; position for transmission to LCD call @lcdCommand ; send it as command jmp mainLoop getKeyboardID ; command code $04: get keyboard ID call @idKeyboard ; prompt keyboard for ID mov stamp_data,keyboard_data ; position HO byte for transmission to Stamp call putStampByte ; send it back to Stamp mov stamp_data,keyboard_0 ; position LO byte for transmission to Stamp call putStampByte ; send it back to Stamp jmp mainLoop ledSettings ; command code $05: set keyboard LEDs call getStampByte ; get parameter from Stamp and keyboard_flags,#$f8 ; strip current settings and stamp_data,#$07 ; ensure Stamp parameter OK or keyboard_flags,stamp_data ; new settings to flags byte call @setLEDs ; set LEDs from new flags byte settings jmp mainLoop scanCodeCheck ; command code $06: check current scan code set call @checkScanCode ; prompt keyboard for scan code set mov stamp_data,keyboard_data ; position for transmission to Stamp call putStampByte ; send it back to Stamp jmp mainLoop scanCodeSet ; command code $07: select scan code set call getStampByte ; get parameter from Stamp call @setScanCode ; set code set selection jmp mainLoop disableStd ; command code $08: set keyboard to standard mode, disabled call @stdDisabled jmp mainLoop enableStd call @stdEnabled ; command code $09: set keyboard to standard mode, enabled jmp mainLoop keyboardEnable ; command code $0a: enable keyboard transmissions call @enableKeyboard jmp mainLoop keyboardEcho ; command code $0b: send echo from keyboard call @sendEcho ; echo command mov Stamp_data,keyboard_data ; position for transmission to Stamp call putStampByte ; send it back to Stamp jmp mainLoop keySend ; command code $0c: get keystroke data from keyboard call @getKey ; do keyboard input routine mov Stamp_data,keyboard_flags ; position for transmission to Stamp call putStampByte ; send it back to Stamp mov Stamp_data,ascii_data ; position for transmission to Stamp call putStampByte ; send it back to Stamp jmp mainLoop ratesSet ; command code $0d: set repeat, typematic rates call getStampByte ; get parameter from Stamp call @setRates jmp mainLoop readLCDChar ; command code $0e: LCD DDRAM read call @readLCD ; read data mov stamp_data,lcd_in ; position for transmission to Stamp call putStampByte ; send it back to Stamp jmp mainLoop setLCDAddress ; command code $0f: set LCD DDRAM address call getStampByte ; get address from Stamp mov lcd_out,stamp_data ; position for transmission to LCD call @lcdCommand ; send as command jmp mainLoop reportLCDAddress ; command code $10: report LCD DDRAM address counter call @getLCDAddress ; get address from LCD mov stamp_data,lcd_in ; position for transmission to Stamp call putStampByte ; send it back to Stamp jmp mainLoop allReturn ; command code $11: send all scan codes as received call @returnAll mov stamp_data,keyboard_flags ; position flags byte for transmission to Stamp call putStampByte ; send it back to Stamp mov stamp_data,keyboard_data ; position scan code for transmission to Stamp call putStampByte ; send it back to Stamp jhi keyboard_flags.valid_data,allReturn ; if empty byte just sent, call it quits jmp mainLoop ;############################################################################### ##################### ; ; Stamp I/O routines ; ;############################################################################### ##################### getStampByte ; accept synchronous serial input from Stamp INPUT stamp_clock_pin ; signal Stamp to begin clocking data out call awaitHighStampClock ; allow internal pullup time to snap line high mov bit_counter,#8 ; get 8 bits call awaitLowStampClock getStampByteBitLoop call awaitHighStampClock ; read data during high clock pulse stc sb rb.stamp_data_pin clc rr stamp_data ; accumulate bits into stamp_data call awaitLowStampClock ; wait for clock to go back low djnz bit_counter,getStampByteBitLoop call awaitIdleStamp ; wait for clock and data to go high (inputs on Stamp) LOW stamp_clock_pin ; suppress Stamp transmissions for now ret putStampByte ; send synchronous serial output to Stamp INPUT stamp_clock_pin ; signal Stamp to start clocking data in call awaitHighStampClock ; allow internal pullup time to snap line high mov bit_counter,#8 ; send 8 bits call awaitLowStampClock putStampByteBitLoop call awaitHighStampClock ; send data during high clock pulse sb stamp_data.0 call setStampDataLow snb stamp_data.0 call setStampDataHigh call awaitLowStampClock ; wait for clock to go back low mov loop_counter,#30 ; leave data on data line for Stamp to read djnz loop_counter,$ rr stamp_data ; position next bit for transmission djnz bit_counter,putStampByteBitLoop INPUT stamp_data_pin ; release data line rr stamp_data ; restore stamp_data with final rotate call awaitIdleStamp ; wait for clock line to go high (INPUT on Stamp) LOW stamp_clock_pin ; suppress Stamp transmissions for now ret setStampDataLow ; set data line to output, low LOW stamp_data_pin ret setStampDataHigh ; release data line to pulled-up high INPUT stamp_data_pin ret awaitIdleStamp ; wait for highs on Stamp data and clock lines jlo rb.Stamp_clock_pin,awaitIdleStamp jlo rb.Stamp_data_pin,awaitIdleStamp jlo rb.Stamp_clock_pin,awaitIdleStamp jlo rb.Stamp_data_pin,awaitIdleStamp jlo rb.Stamp_clock_pin,awaitIdleStamp jlo rb.Stamp_data_pin,awaitIdleStamp ret awaitLowStampClock ; wait for solid low on Stamp clock pin jhi rb.Stamp_clock_pin,awaitLowStampClock jhi rb.Stamp_clock_pin,awaitLowStampClock jhi rb.Stamp_clock_pin,awaitLowStampClock jhi rb.Stamp_clock_pin,awaitLowStampClock ret awaitHighStampClock ; wait for solid high on Stamp clock pin jlo rb.Stamp_clock_pin,awaitHighStampClock jlo rb.Stamp_clock_pin,awaitHighStampClock jlo rb.Stamp_clock_pin,awaitHighStampClock jlo rb.Stamp_clock_pin,awaitHighStampClock ret ;############################################################################### ##################### ;############################################################################### ##################### org $200 ; page 1, main routine ;############################################################################### ##################### ;############################################################################### ##################### ;############################################################################### ##################### ; ; Keyboard I/O routines ; ;############################################################################### ##################### commandTokeyboard ; send command to keyboard ; drive clock low ~150 usec to 'seize' interface...drive data line low and release clock (serves ; as start bit)...keyboard will drive clock low to prompt 8 data bits, (odd) parity bit and (high) ; stop bit...keyboard will drive clock and data low to acknowledge command reception...test for ; follow-on ack byte in response INPUT keyboard_clock_pin ; release keyboard communications hold call awaitIdle ; don't transmit unless interface idle mov loop_counter,#150 ; seize interface by driving clock low LOW keyboard_clock_pin inhibitkeyboard djnz loop_counter,inhibitkeyboard INPUT keyboard_clock_pin ; release clock line LOW keyboard_data_pin ; open clock + low data serves as 'start bit' mov loop_counter,#8 ; prepare to send 8 bits clr temp ; track parity in temp.0 txBitLoop call awaitLowClock ; wait for keyboard to drive clock low sb keyboard_data.0 call setKeyboardDataLow snb keyboard_data.0 call setKeyboardDataHigh xor temp,keyboard_data ; include this bit in parity calculation rr keyboard_data call awaitHighClock ; wait for clock to go back high djnz loop_counter,txBitLoop ; repeat until 8 bits transmitted call awaitLowClock ; send parity bit based on temp.0 snb temp.0 call setKeyboardDataLow sb temp.0 call setKeyboardDataHigh call awaitHighClock ; let clock go back high call awaitLowClock ; send (high) stop bit call setKeyboardDataHigh call awaitHighClock ; let clock go back high call awaitLowClock ; await ack bit from keyboard mov lcd_out,#'k' jhi rb.keyboard_data_pin,oops ; should be low call awaitHighClock ; let clock go back high call getKeyboardByte ; check for response jnc noResponse cmp keyboard_data,keyboard_ack ; test for ack, let caller decide if it matters ret getkeyboardByte ; receive byte from keyboard ; upon low clock from keyboard ensure data line is low (start bit)...get 8 data bits on ; succeeding low clocks...get (odd) parity bit and (high) stop bit on next two clocks INPUT keyboard_clock_pin ; release clock to allow keyboard to transmit clr keyboard_data mov delay0,#5 ;10 ; wait for start bit's low clock, but not forever clr delay1 clr delay2 getkeyboardByteAwait jlo rb.keyboard_clock_pin,keyboardResponding ; if clock line low, keyboard is talking djnz delay2,getkeyboardByteAwait djnz delay1,getkeyboardByteAwait djnz delay0,getkeyboardByteAwait clc ; if no clock, return with no carry = no byte received ret keyboardResponding mov lcd_out,#'h' jhi rb.keyboard_data_pin,oops ; data line should be low (start bit) clr temp ; form parity bit in temp call awaitHighClock ; let clock go back high mov loop_counter,#8 ; get 8 data bits rxBitLoop call awaitLowClock ; wait for keyboard to drive clock low stc ; take a guess sb rb.keyboard_data_pin clc ; guessed wrong if here rr keyboard_data xor temp,rb ; include this bit in parity calculation call awaitHighClock ; wait for clock to return high djnz loop_counter,rxBitLoop ; repeat until 8 bits received call awaitLowClock ; await parity bit mov lcd_out,#'p' xor temp,rb ; see if parity matches and temp,#$01 << keyboard_data_pin jz oops ; if not, problems call awaitHighClock ; wait for clock to return high call awaitLowClock ; await stop bit mov lcd_out,#'s' jlo rb.keyboard_data_pin,oops ; problems if stop bit not high call awaitHighClock LOW keyboard_clock_pin ; suppress keyboard until we're ready to listen stc ; return with carry set = byte received ret setKeyboardDataLow ; set keyboard data line to output, low LOW keyboard_data_pin ret setKeyboardDataHigh ; release data line to pulled-up high INPUT keyboard_data_pin ret awaitIdle ; await idle condition (solid clock and data high) mov loop_counter,#100 awaitIdleLoop jlo rb.keyboard_clock_pin,awaitIdle jlo rb.keyboard_data_pin,awaitIdle djnz loop_counter,awaitIdleLoop ret awaitLowClock ; wait for solid low on clock pin jhi rb.keyboard_clock_pin,awaitLowClock jhi rb.keyboard_clock_pin,awaitLowClock ret awaitHighClock ; wait for solid high on clock pin jlo rb.keyboard_clock_pin,awaitHighClock jlo rb.keyboard_clock_pin,awaitHighClock ret ;############################################################################### ##################### ; ; Keyboard command routines ; ;############################################################################### ##################### keyboardReset ; Initiate keyboard reset and self-test. ; sequence: To keyboard From keyboard Comments ; ffh reset, self-test command ; fah command acknowledgement ; (keyboard conducts self-test) ; aah passed self-test, or ; (fch) failed self-test mov keyboard_data,#keyboard_reset call commandToKeyboard jnz badAck call getKeyboardByte retp idKeyboard ; Prompt keyboard to return ID code ; sequence: To keyboard From keyboard Comments ; f2h return keyboard ID command ; fah command acknowledgement ; ($ab lo byte keyboard ID word (101/102 key) ; $83) ho byte keyboard ID word (101/102 key) or ; ($ab lo byte keyboard ID word (122 key) ; $86) ho byte keyboard ID word (122 key) or ; ($ab lo byte keyboard ID word (84/85 key) ; $84) ho byte keyboard ID word (84/85 key) ; or (w95)??????????? mov keyboard_data,#id_keyboard ; keyboard ID command call commandToKeyboard jnz badAck call getKeyboardByte ; get LO byte of keyboard ID word mov keyboard_0,keyboard_data call getKeyboardByte ; get HO byte of keyboard ID word retp stdEnabled ; Set/reset keyboard to default operating parameters, enabled. ; sequence: To keyboard From keyboard Comments ; $f6 set standard enabled command ; $fa command acknowledgement mov keyboard_data,#enable_std call commandToKeyboard jnz badAck retp stdDisabled ; Set/reset keyboard to default operating parameters, disabled. ; sequence: To keyboard From keyboard Comments ; $f5 set sample rate command ; $fa command acknowledgement mov keyboard_data,#disable_std call commandToKeyboard jnz badAck retp enableKeyboard ; Enable keyboard transfers. ; sequence: To keyboard From keyboard Comments ; $f4 set sample rate command ; $fa command acknowledgement mov keyboard_data,#enable_keyboard call commandToKeyboard jnz badAck retp checkScanCode ; Interrogate keyboard scan code set in current use. Note same keyboard command as select ; scan code set, but parmater byte = 0. ; sequence: To keyboard From keyboard Comments ; f0h scan code set command ; fah command acknowledgement ; 00h interrogate ; fah command acknowledgement ; xxh current scan code set mov keyboard_data,#scan_code call commandToKeyboard jnz badAck clr keyboard_data call commandToKeyboard jnz badAck call getKeyboardByte jnc noResponse retp setScanCode ; Set keyboard scan code set. Send 01h, 02h or 03h after scan code set command to ; select scan code set #1, #2 or #3 respectively. ; sequence: To keyboard From keyboard Comments ; f0h scan code set command ; fah command acknowledgement ; 02h select scan code set #2 ; fah command acknowledgement mov keyboard_data,#scan_code call commandToKeyboard jnz badAck mov keyboard_data,stamp_data call commandToKeyboard jnz badAck retp setLEDs ; Set/reset Caps Lock, Num Lock, Scroll Lock LEDs ; sequence: To keyboard From keyboard Comments ; edh set LEDs command ; fah command acknowledgement ; 0xh parameter (00000CNS) ; fah command acknowledgement mov keyboard_data,#set_leds call commandToKeyboard jnz badAck mov keyboard_data,keyboard_flags and keyboard_data,#$07 call commandToKeyboard jnz badAck retp setRates ; Set typematic auto-repeat, delay parameters. Bit 7 = 0. Bits 6,5 express 1/2 secs for ; hold-down delay before repeating. Bits 4 through 0 express repeat rate: 0 gives ~30/sec, ; 1fh gives ~2/sec. Desired settings must be in command_parm when called. ; sequence: To keyboard From keyboard Comments ; f3h set rates command ; fah command acknowledgement ; xxh parameter (0ddrrrrr) ; fah command acknowledgement mov keyboard_data,#set_rates call commandToKeyboard jnz badAck mov keyboard_data,stamp_data call commandToKeyboard jnz badAck retp sendEcho ; Responds with eeh. ; sequence: To keyboard From keyboard Comments ; eeh send echo command ; eeh command echo mov keyboard_data,#send_echo call commandToKeyboard retp returnAll ; Allow keyboard to transmit scan code. Set valid_data flag bit if anything received. clrb keyboard_flags.valid_data ; default = nothing received call getKeyboardByte ; look for keyboard data snc ; set valid_data flag bit if input received setb keyboard_flags.valid_data retp getKey ; Allow keyboard to transmit scan code(s). Set valid_data flag bit if "make" scan code. ; Adjust keyboard_flags and LEDs for selected "make" and "break" codes. Convert code(s) ; to ASCII representation for Stamp. clrb keyboard_flags.valid_data ; default: no real key press in data returned call getKeyboardByte sc ; bail out now if nothing from getKeyboardByte retp jhi keyboard_data.7,highScanCodes ; handle scan codes $80 to $ff separately setb keyboard_flags.valid_data ; let Stamp know real key press in data returned cmp keyboard_data,$69 ; handle keypad (scan code > $69) indexing uniquely jge keypad snb keyboard_flags.shift_down setb keyboard_data.7 ; use shifted lookup table if shift key depressed mov w,#$06 ; ASCII table in page 3 mov m,w ; mode register used as base address mov w,keyboard_data ; use [modified] scan code as displacement into ASCII table iread ; fetch ASCII value for scan code mode DIR ; restore m register to DIR value mov ascii_data,w jlo keyboard_flags.caps_locked,asciiCodeReady ; swap cases only for alpha characters if caps lock in effect cmp ascii_data,$41 ; ascii data < $41 ('A')? jlt asciiCodeReady ; if so, non-alpha cmp ascii_data,$7b ; ascii data > $7a ('z')? jge asciiCodeReady ; if so, non-alpha cmp ascii_data,$5b ; ascii data <= $5a ('Z')? jlt toggleCases ; if so, alpha cmp ascii_data,$61 ; ascii data <= $60 ('a')? jlt asciiCodeReady ; if so, non-alpha toggleCases xor ascii_data,#$20 asciiCodeReady jhi ascii_data.7,pseudoAscii ; handle "pseudoASCII" $80 to $ff uniquely retp keypad snb keyboard_flags.num_locked ;1 setb keyboard_data.7 mov w,#$80 snb keyboard_flags.3 ; add/take back $80 if right or left shift in effect xor keyboard_data,w mov w,#$06 ; ASCII table in page 3 mov m,w ; mode register used as base address mov w,keyboard_data ; use [modified] scan code as displacement into ASCII table iread ; fetch ASCII value for scan code mode DIR ; restore m register to DIR value mov ascii_data,w jhi ascii_data.7,pseudoAscii ; handle "pseudoASCII" $80 to $ff uniquely retp pseudoAscii ; scan codes < $80, pseudo-ASCII > $7f cmp ascii_data,$93 ; caps lock key jz capsLock cmp ascii_data,$91 ; left shift key jz capShift cmp ascii_data,$94 ; right shift key jz capShift cmp ascii_data,$a1 ; num lock key jz numLock cmp ascii_data,$8e ; scroll lock key jz scrollLock cmp ascii_data,$90 ; left alt key jz alt cmp ascii_data,$95 ; right alt key jz altRight cmp ascii_data,$92 ; left ctrl key jz ctrl cmp ascii_data,$96 ; right ctrl key jz ctrlRight retp capsLock xor keyboard_flags,#$01 << caps_locked ; toggle caps lock in effect flag jmp setLEDs capShift setb keyboard_flags.3 ; set shift in effect flag retp numLock xor keyboard_flags,#$01 << num_locked ; toggle num lock in effect flag jmp setLEDs scrollLock xor keyboard_flags,#$01 << scroll_locked ; toggle scroll lock in effect flag jmp setLEDs alt setb keyboard_flags.alt_down retp altRight xor keyboard_flags,#$01 << alt_down ; no break code for undo, so toggle retp ctrl setb keyboard_flags.ctrl_down retp ctrlRight xor keyboard_flags,#$01 << ctrl_down ; no break code for undo, so toggle retp highScanCodes ; scan codes > $7f cmp keyboard_data,$f0 ; key released? jz keyReleased mov ascii_data,#'-' cmp keyboard_data,$84 ; numeric pad "-"? jz highScanCodesOK mov ascii_data,#$a2 ; left win key cmp keyboard_data,$8b jz win mov ascii_data,#$a3 ; right win key cmp keyboard_data,$8c jz win mov ascii_data,#$a4 ; context menu key cmp keyboard_data,$8d jnz lostInSpace highScanCodesOK setb keyboard_flags.valid_data ; let Stamp know real key press in data returned retp win setb keyboard_flags.win_down jmp highScanCodesOK keyReleased ; $f0 scan code already received...get, analyze, take any required action on remainder of release sequence ; ignore $f0,$14 (caps lock key release) and $f0,$8d (context menu release) sequences call getKeyboardByte cmp keyboard_data,$12 ; left shift? jz releaseShift cmp keyboard_data,$59 ; right shift? jz releaseShift cmp keyboard_data,$19 ; left alt? jz releaseAlt cmp keyboard_data,$11 ; left ctrl? jz releaseCtrl cmp keyboard_data,$8b ; left win? jz releaseWin cmp keyboard_data,$8c ; right win? jz releaseWin retp releaseShift ; left or right shift key has been released...clear left/right shift flag clrb keyboard_flags.shift_down retp releaseAlt ; left alt key has been released...clear left/right alt flag clrb keyboard_flags.alt_down retp releaseCtrl ; left ctrl key has been released...clear left/right ctrl flag clrb keyboard_flags.ctrl_down retp releaseWin ; left or right win key has been released...clear left/right win flag clrb keyboard_flags.win_down retp noResponse ; keyboard sent no response to command mov lcd_out,#'-' jmp oops badAck ; keyboard response to command wasn't ack mov lcd_out,#'@' jmp oops lostInSpace ; invalid keystroke data mov tst,keyboard_data call @debug mov tst,ascii_data call @debug mov lcd_out,#'L' oops ; critical error in reception occurred call @lcdData jmp $ ;############################################################################### ##################### ;############################################################################### ##################### org $400 ; page 2, debug, LCD and utility routines ;############################################################################### ##################### ;############################################################################### ##################### initLCD mov delay2,#20 ; let LCD do internal initialization call delay clrb rc.lcd_rs ; set LCD register select to command clrb rc.lcd_rw ; set LCD R/W to write IF external_reset mov lcd_out,#%00110000 ; send external reset sequence call nibbleToLCD mov lcd_out,#%00110000 call nibbleToLCD call pause120 mov lcd_out,#%00110000 call nibbleToLCD ENDIF mov lcd_out,#%00100000 ; 4-bit i'face (special 1-time 4-bit command) call nibbleToLCD mov lcd_out,#%00101000 ; 4-bit i'face, 2 lines, 5 x 7 call @lcdCommand mov lcd_out,#%00001111 ; display on, blinking cursor call @lcdCommand mov lcd_out,#%00000110 ; cursor increments, no shift call @lcdCommand mov lcd_out,#%00000001 ; clear display, cursor home call @lcdCommand retp lcdCommand call awaitNotBusy ; ensure execution of last command complete clrb rc.lcd_rs ; send register select low (sending command) clrb rc.lcd_rw ; send R/W low jmp byteToLCD lcdData call awaitNotBusy ; ensure execution of last command complete setb rc.lcd_rs ; send register select high (sending data) clrb rc.lcd_rw ; send R/W low byteToLCD call nibbleToLCD ; send HO nibble to LCD call nibbleToLCD ; send LO nibble to LCD retp nibbleToLCD swap lcd_out ; swap lcd_out nibbles mov ra,lcd_out ; preload LCD data lines to ra's LO nibble setb rc.lcd_en ; send LCD enable high mode DIR ; assert LCD data lines mov !ra,#%0000 call pause480 ; pause 480 nsec (require >= 360 nsec 'data delay time') clrb rc.lcd_en ; send LCD enable back low mov !ra,#%1111 ; return LCD data pins to input call pause540 ; pause 540 nsec (require >= 1000 nsec enable cycle time) ret awaitNotBusy clrb rc.lcd_rs setb rc.lcd_rw ; set up busy flag read jmp $+1 ; 60 nsec @ 50 MHz (require >= 60 nsec 'address set-up time') setb rc.lcd_en ; send LCD enable high call pause480 ; pause 480 nsec (require >= 360 nsec 'data delay time') mov lcd_in,ra ; read LCD data clrb rc.lcd_en ; send LCD enable back low call pause540 ; pause 540 nsec (require >= 1000 nsec enable cycle time) setb rc.lcd_en ; send LCD enable back high for second read call pause480 ; pause 480 nsec (require >= 360 nsec 'data delay time') nop ; ignore low order nibble--only want busy flag status clrb rc.lcd_en ; send LCD enable back low again awaitNotBusyPause djnz delay0,awaitNotBusyPause ; let LCD do some work after each busy flag check sb lcd_in.lcd_busy ; if busy flag set, repeat ret ; else return jmp awaitNotBusy pause540 ; call here gives 540 nsec pause @ 50 MHz jmp $+1 pause480 ; call here gives 480 nsec pause @ 50 MHz jmp $+1 jmp $+1 jmp $+1 jmp $+1 jmp $+1 pause180 ; call here gives 180 nsec pause @ 50 MHz jmp $+1 pause120 ; call here gives 120 nsec pause @ 50 MHz ret delay djnz delay0,delay djnz delay1,delay djnz delay2,delay ret readLCD ; read DDRAM contents at cursor call awaitNotBusy clr lcd_in setb rc.lcd_rs setb rc.lcd_rw ; set up DDRAM data read jmp $+1 ; 60 nsec @ 50 MHz (require >= 60 nsec 'address set-up time') setb rc.lcd_en ; send LCD enable high call pause480 ; pause 480 nsec (require >= 360 nsec 'data delay time') mov lcd_in,ra ; read high order nibble of LCD data swap lcd_in clrb rc.lcd_en ; send LCD enable back low call pause540 ; pause 540 nsec (require >= 1000 nsec enable cycle time) setb rc.lcd_en ; send LCD enable back high for second read call pause480 ; pause 480 nsec (require >= 360 nsec 'data delay time') or lcd_in,ra ; get low order nibble of LCD data clrb rc.lcd_en ; send LCD enable back low again retp getLCDAddress ; read DDRAM location counter call awaitNotBusy clr lcd_in clrb rc.lcd_rs setb rc.lcd_rw ; set up DDRAM address read jmp $+1 ; 60 nsec @ 50 MHz (require >= 60 nsec 'address set-up time') setb rc.lcd_en ; send LCD enable high call pause480 ; pause 480 nsec (require >= 360 nsec 'data delay time') mov lcd_in,ra ; read LCD data swap lcd_in clrb rc.lcd_en ; send LCD enable back low call pause540 ; pause 540 nsec (require >= 1000 nsec enable cycle time) setb rc.lcd_en ; send LCD enable back high for second read call pause480 ; pause 480 nsec (require >= 360 nsec 'data delay time') or lcd_in,ra ; get low order nibble clrb rc.lcd_en ; send LCD enable back low again retp init ; clear registers, initialize I/O pins bank Zero ; direct attention to bank 0 mov fsr,#$08 ; clear all application registers clrMem1 ; clear $08-$1f clr ind inc fsr jlo fsr.5,clrMem1 clrMem2 ; clear $30-$3f, $50-$5f,...$f0-$ff setb fsr.4 clr ind incsz fsr jmp clrMem2 bank Zero ; redirect attention to bank 0 clr ra clr b_pins mov rb,b_pins clr rc mode PLP mov !rb,#%00000000 ; use internal pullups to accomplish open drain configuration mov !rc,#%00000000 mode DIR ; set input/output states mov !ra,#%1111 ; LCD data lines inputs to start mov b_dirs,#%00001011 ; keyboard clock & data inputs to start, stamp clock output low to start mov !rb,b_dirs mov !rc,#%10001111 ; LCD control lines outputs to start retp debug ; send binary representation of byte in tst to LCD mov bit_counter,#8 ; show each bit in the byte debugLoop mov w,#$30 ; represent as '0' or '1' snb tst.7 mov w,#$31 mov lcd_out,w call lcdData rl tst ; rotate next bit into tst.0 djnz bit_counter,debugLoop ; repeat until 8 bits processed mov lcd_out,#$20 ; append 2 spaces for readability call lcdData mov lcd_out,#$20 call lcdData retp ;############################################################################### ##################### ;############################################################################### ##################### org $600 ; page 3: scan code to ASCII/pseudoASCII lookup tables ;############################################################################### ##################### ;############################################################################### ##################### ; lower case ASCII/pseudoASCII codes, indexed by scan code (code set 3), $ff = invalid dw $ff,$ff,$ff,$ff,$ff,$ff,$ff,$81,$1b,$ff,$ff,$ff,$ff,$9,'`',$82 dw $ff,$92,$91,$ff,$93,'q1',$83,$ff,$90,'zsaw2',$84 dw $ff,'cxde43',$85,$ff,$20,'vftr5',$86 dw $ff,'nbhgy6',$87,$ff,$95,'mju78',$88 dw $ff,',kio09',$89,$ff,'.',$2f,'l;p-',$8a dw $ff,$ff,$27,$ff,'[=',$8b,$8d,$96,$94,$0d,']\',$ff,$8c,$8e dw $9f,$9e,$8f,$9d,$7f,$9b,$8,$97,$ff,$9b,$a0,$9e,$98,$9c,$98,$99 dw $97,$7f,$9f,' ',$a0,$9d,$a1,$2f,$ff,$0d,$9c,$ff,'+',$99,'*',$ff ; upper case ASCII/pseudoASCII codes, indexed by scan code (code set 3), $ff = invalid dw $ff,$ff,$ff,$ff,$ff,$ff,$ff,$81,$1b,$ff,$ff,$ff,$ff,$9,'~',$82 dw $ff,$92,$91,$ff,$93,'Q!',$83,$ff,$90,'ZSAW@',$84 dw $ff,'CXDE$#',$85,$ff,$20,'VFTR%',$86 dw $ff,'NBHGY^',$87,$ff,$95,'MJU&*',$88 dw $ff,'?L:P_',$8a dw $ff,$ff,'"',$ff,'{+',$8b,$8d,$96,$94,$0d,'}|',$ff,$8c,$8e dw $9f,$9e,$8f,$9d,$7f,$9b,$8,$97,$ff,'1',$a0,'47',$9c,$98,$99 dw '0.2568',$a1,$2f,$ff,$0d,'3',$ff,'+9*',$ff