Department of InformatiX
Microsoft .NET Micro Framework Tools & Resources

Now we have everything ready to finish our series about buttons and input system extending by connecting a matrix keyboard.

Matrix keyboard principles

Matrix keyboard is a couple of buttons in a matrix, where there is one wire for each column and one for each row. For example, if you have 12 buttons arranged into a 3×4 matrix, then there is a total number of 7 wires. Pressing a button connects corresponding column and row wires together. I could have written a detailed description about it and how can one scan the wires to find out what button is pressed, but I would just duplicate the great illustrative intro written by Dave Dribin. So in the rest of this article I expect you know how the matrix keyboard works and when ghosting happens. Matrix keyboards usually don't have any electronics inside (just wires), so you can connect them directly to a processor or whatever you need.

STD 34-07 and STD 44-08

There are lot of matrix keyboards available. I've used an universal membrane keypad available at Transfer Multisort Elektronik for around €5 (available in Poland, United Kingdom, Germany, Russia, Romania, Hungary, Czech Republic, Slovakia and Bulgaria). It comes with a DIP connector and the STD 44-08 model looks like this:

STD 44-80 schemaSTD 44-08 photo

The STD 34-07 is the same but does not have the right column (so the connector has only 7 pins) and you can both get with matching boxes made by Hammond Mfg.

I've enumerated the buttons available in the following enum:

public enum KeypadButton { Shift = Button.Menu, Enter = Button.Select, Up = Button.Up, Left = Button.Left, Right = Button.Right, Down = Button.Down, Decimal = Button.Pause, Clear = Button.Back, Add = Button.Play, Multiply = Button.FastForward, Substract = Button.Stop, Divide = Button.Rewind, NumPad0 = Button.Last + 1, NumPad1, NumPad2, NumPad3, NumPad4, NumPad5, NumPad6, NumPad7, NumPad8, NumPad9, F1, F2, F3, F4, F5 }

Note that I have defined all the buttons the keys can represent, not the actual keys (i.e. both NumPad7 and F2), and that I've tried to use as much standard Button enum values as possible, and match their meaning.

MatrixKeyboard driver

The driver can be downloaded here. It contains the following design:

MatrixKeyboard class schema

The MatrixKeyboard is the main driver class, all others are nested. The KeyLocation structure just holds a Column-Row pair, nothing special about it. It supports comparing and since the coordinates are of int type, whole structure can be casted to and from long.

The KeyStateArray keeps track of which keys are pressed and which not. So it is basically keeping a true/false value for each item in a matrix, like bool[,] state. However, we don't have multi-dimensional arrays and storing bool values in such manner would be very inefficient. So this class uses an array of int to pack all the boolean values into bits. Even data for 5×6 keyboard which would take 30 bits can be stored in a single integer. Moreover, with an indexer and some shifting and masking we can still access the underlaying data using the state[row, column] syntax.

There are two more methods. The static IsCompatible(KeyStateArray a, KeyStateArray b) is easy, it just checks whether two KeyStateArrays are for the same row and column count. The most interesting one is

public static void FindChanges(KeyStateArray oldState, KeyStateArray newState, out ArrayList pressed, out ArrayList released)

It takes two KeyStateArrays and compares the stored values. It outputs two lists, one which contains list of KeyLocations where there is false in the oldState and true in the newState (the corresponding key was pressed), and the other contains list of locations with the opposite difference (the key was released).

Okay, let's show how the MatrixKeyboard class works:

StartListening
Constructor
output & input port initialization
start event thread
Event thread
fire events
wait for
signal
pick up
pending list
Any of InterruptPorts interrupt
stop listeningdetach interrupts
outputs off
get current state
find changes
for each output
check all inputs
add changes to event list
start listeningoutputs on
attach interrupts
signal event thread
EventKeyDown
EventKeyUp

The first step to begin with is to instantiate it (yes, you can have multiple keyboards connected). To be able to call the constructor, you need to supply a list of pins to which the row wires are connected, list of pins to which the column wires are connected and a resistor mode of the processor. The class is able to work with both pull-ups and pull-downs, but requires the processor to support catching interrupts on both edges and - unless you turn it off - glitch filter at the same time. The constructor decides whether active (output) ports will be rows or columns. This affects two things. The first is performance - toggling a state on an OutputPort is very time expensive in comparison to reading a state of InterruptPort, so you want to have as few active ports as possible. The second thing is ghosting. Without any additional tasks to eliminate ghosting, only single button will be detected in the passive direction, so you want to have as much passive ports as possible. Cool, not everyday happens that requirements are not against each other, so the decision is simple - active is the direction with fewer wires. If you have any reason to have it otherwise, just modify the _activeAreRows variable appropriately. Then the ports are instantiated and the eventThread (which we will discuss in a minute) is created and started.

So assuming you have a 3×4 keyboard and pins defined using Cpu.Pin constants in say... Connected static class:

MartixKeyboard Keypad = new MatrixKeyboard( new Cpu.Pin[] { Connected.Row1, Connected.Row2, Connected.Row3, Connected.Row4 }, new Cpu.Pin[] { Connected.Column1, Connected.Column2, Connected.Column3 }, Port.ResistorMode.PullUp);

But it still does not do much. We have to start monitoring the ports:

Keypad.StartListening();

This method just writes on to all the active ports and attach interrupt handlers to all passive ones (in this order, or we would have interrupts caused by ourselves). The passive ports are floating when there is no key pressed (this is why you need some kind of resistor pulls). So what happens when you press a key? The on value will get on the passive port and cause an interrupt. During the interrupt, the listening is stopped. The interrupt handlers are detached and off is written to all the active ports (in this order, you know...).

Then, the GetCurrentState method is called, which scans the keyboard into a KeyStateArray. In fact you can use the driver in 'manual' mode, that is, you will not start listening and just check the actual keys state using this method. The scanning is done by reading all passive ports for each active one after setting it to on, see the David's page for details and pictures. The current state is compared to the old state using the FindChanges mentioned above and changes are added to the _eventBuffer list in a form of EventBufferItem. This class just holds information needed to fire the KeyPressed/KeyReleased events. Finally, the event thread is signaled that the pending events list was updated.

The only task for the event thread is to fire events. Its startup method is EventThread() and it spends most of its lifetime waiting for a signal. When it gets one, it picks up an EventBufferItem from the _eventBuffer FIFO and fires a corresponding event until the list is empty again.

In order to catch more than one key in the passive direction, you would have to scan the keyboard state permanently in a loop until all keys are released, which would be CPU extensive and I don't think it is worth that. If you do, let me know if you have any troubles implementing it.

MatrixKeyboardInputProvider

Now we need to take the pressed key and send a ButtonDown event with the button the key represents. This is a task for an input provider, which can be browsed here. It can be divided into two parts. The set-up one, containing RegisterButton and RegisterModifier overloads and the runtime one which consists mainly of the KeyDown and KeyUp driver's event handlers. This is the class schema:

MatriKeyboardInputProvider class schema

For the details about pushing extended button values throughout the WPF input system (ReportButton and RaiseEvent methods), see the Pushing the limits of thirteen Buttons article.

If you look at the martix keyboard we have, the keys can have different meanings depending on whether the Shift key is pressed or not. For the case your keyboard has different modifiers I've defined the whole enum available in WPF:

namespace System.Windows.Input { [Flags] public enum ModifierKeys { None = 0, Alt = 1, Control = 2, Shift = 4, Windows = 8 } }

So basically, we need to map a Button to a KeyLocation. You do that using the RegisterButton method:

KeypadInput = new MatrixKeyboardInputProvider(Keypad, null); KeypadInput.RegisterButton(new MatrixKeyboard.KeyLocation(3, 2), ModifierKeys.None, (Button)KeypadButton.Enter); KeypadInput.RegisterButton(new MatrixKeyboard.KeyLocation(3, 2), ModifierKeys.Shift, (Button)KeypadButton.Clear);

This code says, we have Enter in the fourth row and third column unless the shift modifier is active, in which case it is the Clear button. The method just creates a ButtonTuplet with the parameters you have supplied and adds it to the _registeredButtons storage.

Now we also need to register the modifiers. Modifiers can behave differently. For example, the Shift key is usually active only while you keep it pressed, and Num Lock can be toggled on and off with multiple presses. You can set keys like Shift to remain pressed after you release them until you press any other standard key (this feature is called Sticky Keys and can be found in the accessibility settings of your computer). All these values are represented in the following enum:

namespace UAM.InformatiX.SPOT.Hardware { public enum ModifierBehavior { Normal, StickyKey, ToggleKey } }

The modifier registration is pretty much the same:

KeypadInput.RegisterModifier(new MatrixKeyboard.KeyLocation(3, 0), ModifierKeys.Shift, ModifierBehavior.StickyKey);

Which says we want the key in first column and fourth row to work as sticky Shift modifier. As above, a ModifierTuplet is constructed using parameters you have supplied and added to the _registeredModifiers storage.

The event handlers are quite simple. The KeyDown checks if there is any button registered for the KeyLocation pressed (and any active modifier) and if so, it reports it as a ButtonDown event. The same does KeyUp handler for the ButtonUp event. The difference is in working with modifier keys. The KeyDown handler firstly checks whether the location does represent a registered modifier. If so, it is added to or removed from the _currentModifiers list, depending on its behavior. Any temporary modifiers, which are in fact only the sticky ones, will be removed upon key up event of any standard key.

Modifier key presses are not reported anywhere, just the resulting buttons. However, you can bind to the MatrixKeyboardInput.CurrentModifierChanged event if you want to signal currently active modifiers in your UI. There are available as the CurrentModifier property which is only union view of the _currentModifiers list. Also note that it may be not only difficult for the user to work with standard modifier behavior on embedded devices, but it also requires you to be able to catch multiple keys simultaneously, which might require additional work in the case of matrix keyboard - so I recommend using sticky or toggle behavior.

When defining all the keys on the keyboard, you might want to take advantage of the other overloads (which also have better performance than registering the buttons one by one). Here is the complete registration for STD 34-07:

KeypadInput.RegisterModifier(new MatrixKeyboard.KeyLocation(3, 0), ModifierKeys.Shift, ModifierBehavior.StickyKey); KeypadInput.RegisterButton( // maps key locations to buttons when no modifier is active new MatrixKeyboard.KeyLocation[] { new MatrixKeyboard.KeyLocation(0, 0), new MatrixKeyboard.KeyLocation(0, 1), new MatrixKeyboard.KeyLocation(0, 2), new MatrixKeyboard.KeyLocation(1, 0), new MatrixKeyboard.KeyLocation(1, 1), new MatrixKeyboard.KeyLocation(1, 2), new MatrixKeyboard.KeyLocation(2, 0), new MatrixKeyboard.KeyLocation(2, 1), new MatrixKeyboard.KeyLocation(2, 2), /* SHIFT */ new MatrixKeyboard.KeyLocation(3, 1), new MatrixKeyboard.KeyLocation(3, 2) }, ModifierKeys.None, new Button[] { (Button)KeypadButton.NumPad7, (Button)KeypadButton.NumPad8, (Button)KeypadButton.NumPad9, (Button)KeypadButton.NumPad4, (Button)KeypadButton.NumPad5, (Button)KeypadButton.NumPad6, (Button)KeypadButton.NumPad1, (Button)KeypadButton.NumPad2, (Button)KeypadButton.NumPad3, /* SHIFT */ (Button)KeypadButton.NumPad0, (Button)KeypadButton.Enter, }); KeypadInput.RegisterButton( // maps key locations to buttons while the Shift modifier is active new MatrixKeyboard.KeyLocation[] { new MatrixKeyboard.KeyLocation(0, 0), new MatrixKeyboard.KeyLocation(0, 1), new MatrixKeyboard.KeyLocation(0, 2), new MatrixKeyboard.KeyLocation(1, 0), new MatrixKeyboard.KeyLocation(1, 1), new MatrixKeyboard.KeyLocation(1, 2), new MatrixKeyboard.KeyLocation(2, 0), new MatrixKeyboard.KeyLocation(2, 1), new MatrixKeyboard.KeyLocation(2, 2), /* SHIFT */ new MatrixKeyboard.KeyLocation(3, 1), new MatrixKeyboard.KeyLocation(3, 2) }, ModifierKeys.Shift, // this overload assumes all the button mappings are for the same modifier new Button[] { (Button)KeypadButton.F4, (Button)KeypadButton.Up, (Button)KeypadButton.F5, (Button)KeypadButton.Left, (Button)KeypadButton.F3, (Button)KeypadButton.Right, (Button)KeypadButton.F1, (Button)KeypadButton.Down, (Button)KeypadButton.F2, /* SHIFT */ (Button)KeypadButton.Decimal, (Button)KeypadButton.Clear, }); Keypad.StartListening(); // don't forget to turn the keyboard driver on, it's a way more fun then :)

Download all the classes and enums in one pack here.

Additional notes:

Enjoy!

Comments
Sign in using Live ID to be able to post comments.