Bascom and AVR, Keyboard


Most small controller applications use one or more buttons or switches for user input. When things get a little more complicated, a small keyboard can be a good solution. These keyboards come in 3x4 or 4x4 variants, most often the keyswitches are arranged in a matrix:



The matrix is used because it minimises the number of connections from the keyboard to the controller. A 4x4 keyboard has 16 keyswitches. You could wire all 16 switches to the controller, but a more clever solution is to arrange the keys in a matrix and then wire the four columns and rows to the controller.
With most keyboards, the 0 ... 9 keys are in the same place. They differ in the location and marking of the other keys.
How to use such a keyboard? There are a number of methods. I use one eight-bit port of the controller. The columns are wired to the upper four bits, the rows to the lower four bits. Now, a small difficulty: once a key is pressed, an interrupt should be generated and the controller should read which key is pressed. The best solution would be if the controller could generated an injterrupt on >any< change in the state of the rows. A PIC controller can do this, but the small AT90S2313 only has two interrupt pins. The solution is to add four diodes and a pull-up resistor to make an OR-gate wired to Int0:


The 470 Ohm resistors in the row and column leads protect the controller in case there is a direct connection through a keyswitch between a high output and a low output pin. This is a theoretical possibility, you can do without them
The Int0 input pin has a 10k pull-up to Vcc. We make the upper four bits of PortB function as output with a logic low. The lower four bits are made to funtion as input with a logic high. Now, if a key is pressed, a connection is made between a column (output, logic low) and a row (input, logic high). This row line will be pulled low. Also, the row input to the Diode-OR goes low, the OR output goes low, and Int0 fires an interrupt.
In the interrupt routine we quickly read the state of the row lines to see on which row the pressed key is. Then, the upper four bits of PortB are made input with logic level high, and the lower four bits are made output with logic level low. Now, we read the state of the upper four bits to determine on which column the pressed key is. We then wait a certain 'debounce' time. From the state of the row and column lines read, the key pressed is determined. Lastly, the state of the row and column bits of PortB is restored to the starting point.
Which key has been pressed is then determined from the state of the row and column bits:



Of course, the chosen arrangement of row and columnnumbers and keycodes is completely arbitrary. You can choose an entirely different arrangement if that suits you better. (And actually, the arrangement I used in the <../i2c/i2c.html#keyboard>I2C keyboard example is different)
An example of a keyboard decoding program:
keyboard-int.bas
$regfile = "2313def.dat"
$crystal = 4000000

$baud = 9600

Const Debouncetime = 150

Config Pind.6 = Output
Config Pind.2 = Input

Config Int0 = Falling
Dim Wtime As Byte
Dim Keycoderow As Byte
Dim Keycodecol As Byte
Dim Keycode As Byte
Dim Keychar As String * 1

On Int0 Button

'set upper nibble of portb to output, lower to input
Ddrb = &B11110000
Portb = &B00001111
Wtime = 255

Print "ready..."

Enable Interrupts
Enable Int0

Do
  Set Portd.6
  Waitms Wtime
  Reset Portd.6
  Waitms Wtime
Loop

Button:
  Waitms Debouncetime
  'read portb pins to determine which row is zero
  Keycoderow = Pinb
  'set portb upper nibble to input, lower to output
  Ddrb = &B00001111
  Portb = &B11110000
  'give port time to settle
  Waitms 1
  'read portb pins to determine which col is zero
  Keycodecol = Pinb
  'set portb back to original state
  Ddrb = &B11110000
  Portb = &B00001111
  'make keycode from portb pins read
  Select Case Keycoderow
    Case 7 : Keycode = 0
    Case 11 : Keycode = 4
    Case 13 : Keycode = 8
    Case 14 : Keycode = 12
    Case Else : Keycode = 99
  End Select
  'shift upper nibble to lower nibble
  Shift Keycodecol , Right , 4
  'make final keycode from portb pins read
  Select Case Keycodecol
    Case 7 : Keycode = Keycode + 0
    Case 11 : Keycode = Keycode + 1
    Case 13 : Keycode = Keycode + 2
    Case 14 : Keycode = Keycode + 3
    Case Else : Keycode = Keycode + 99
  End Select
  'illegal keycode from bounce effects
  If Keycode > 15 Then Keycode = 16
  Print Keycoderow ; " " ; Keycodecol ; " " ; Keycode
  Keychar = Lookupstr(keycode , Keycodes)
  Print Keychar
  Gifr = 64
Return

End

Keycodes:
Data "1" , "2" , "3" , "A" , "4" , "5" , "6" , "B" ,
Data "7" , "8" , "9" , "C" , "R" , "0" , "E" , "D" , "?"
The mapping from keycode to key character is in the Keycodes Data block. If you have another keyboard with different key assignement, the Data block must be updated.
The key character is sent to the PC through the RS232 interface as all of the PortB pins are assigned to reading the keyboard and the Lcd had to be disconnected.

Note that Bascom has a Getkbd command that can be used to scan a 3x4 or 4x4 keyboard. However, I could not get it to work properly inside an interrupt routine.

TOC