Close

The f/Software Part 1: Key-mapping.

A project log for The fsociety Keyboard

(Mr.Robot's) fsociety inspired Mechanical keyboard. Wirelessly sense, assist and automate your good and bad computing activity.

Pamungkas SumastaPamungkas Sumasta 09/04/2016 at 00:063 Comments

In case you are the ones who have enough guts to try replicate this keyboard, there is one basic thing you should get yourself covered; that is the key input matrix and how it is being mapped. In your previous projects, chances are you only used a couple of buttons or more and each is connected to single IO pin of your microcontroller. Now if you are talking about making a keyboard with more than 50 or even 100 input keys, a more efficient way needs to be implemented. (Except if you have FPGA scattered around with >100 GPIOs and you happen to have an alien friend from another planet that can type millions key stroke per second, then it is different story)

Quite frankly, there are tons of techniques and method to tackle this situation. If you think there is only one type of input Matrix scanning, then you probably have had a miserable life. This one extremely useful site explains in great details about all those key matrix methods (un)commonly used. They even compiled this huge impressive list comparing the number of input and free pins required for all different techniques. It also color coded the classification of additional hardware required. MY GOD.

After some quick consideration, I decided to opt for Single Multiplexed option. There are several reasons why I picked this:

  1. No additional hardware required.
    1. Though technically you need a diode to prevent a click being 'backfired' during the scanning. But setting inactive pins as input and high impedance in every single loops actually solve the problem. In my previous log, I mentioned I put diode in the schematic, but improvised with resistor instead during the build time.
  2. I have enough hardware pins.
    1. I decided to go with ATMEGA2560 with plenty of available input pins. So this shouldn't be a hurdle at all. Because technically you can add extra fancy hardware that does the scanning for you to save pin-count etc but meh, extra stuff in the BOM.
  3. Pretty fast.
    1. Even if you run on basic ATMEGA2560 running at 16mhz, you still can achieve pretty swift scanning loop. For human input device that is far more than enough. The only thing is that you might need to compensate loosing other clock count if you have other thing running the loop in your application. (Remember this is primitive 8-bit uC, no fancy features).
  4. Keypad Library.
    1. I came across with this impressively built keypad library. Works really well. Will save my time and does the job.

So as you probably have seen in earlier log, the schematic for the key switches is provided below:

There are 5 Rows and 14 Columns. So in total we require 19 GPIOs from the uC. I've mentioned earlier about the Keypad library. But I haven't mentioned to you why I picked this one instead of scratching to develop a new scanning techniques. Here's why:

I owe big time to the people that have developed this library.

Alright, once you understand the hardware configuration and the hardware selection, we can go a bit deeper to the real source code itself. Well, first and foremost as you might expect, we need to define the ROW and COLUMN count in the software as shown below.

const byte ROWS = 5; 
const byte COLS = 14; 
Easy right. And then assign the pins accordingly to the GPIOs of the uC.
byte rowPins[ROWS] = {A0, A1, A2, A3, A4};
byte colPins[COLS] = {A5, A6, A7, A8, A9, A10, A11, 22, 23, 24, 25, 26, 27, 28}; 
That can technically be swapped around, but for the sake of readability, I assigned it the way it is connected on the schematic sheet. And up to this point, you technically have configured and matched the hardware and the software accordingly. The next step is to map, which buttons are for which. To do that, you need to assign a special lookup table. Which is nothing but a 2-dimensional constant array.
char keys[ROWS][COLS] = {
  {KEY_ESCAPE,KEY_1,KEY_2,KEY_3,KEY_4,KEY_5,KEY_6,KEY_7,KEY_8,KEY_9,KEY_0,KEY_MINUS,KEY_EQUAL,KEY_BACKSPACE},
  {KEY_TAB,KEY_Q,KEY_W,KEY_E,KEY_R,KEY_T,KEY_Y,KEY_U,KEY_I,KEY_O,KEY_P,KEY_BRACKET_LEFT,KEY_BRACKET_RIGHT,KEY_BACKSLASH},
  {KEY_CAPS_LOCK,KEY_A,KEY_S,KEY_D,KEY_F,KEY_G,KEY_H,KEY_J,KEY_K,KEY_L,KEY_SEMICOLON,KEY_APOSTROPHE,KEY_RETURN,KEY_NONE},
  {KEY_SHIFT_LEFT,KEY_Z,KEY_X,KEY_C,KEY_V,KEY_B,KEY_N,KEY_M,KEY_COMMA,KEY_PERIOD,KEY_SLASH,KEY_SHIFT_RIGHT,KEY_NONE,KEY_NONE},
  {KEY_CONTROL_LEFT,KEY_GUI,KEY_ALT_LEFT,KEY_NONE,KEY_SPACE,KEY_NONE,KEY_DELETE,KEY_APPLICATION,KEY_CONTROL_RIGHT,KEY_NONE,KEY_NONE,KEY_NONE,KEY_NONE,KEY_NONE}
};

The table above depicts the correlation between the ROWS and COLS key matrix. For example, if the ROWS 0 and COLS 0 are are triggered, it then will know that the KEY_ESCAPE needs to be transmitted. And if you also notice, the table is actually arranged according the board layout for the sake of clarity.

On top of that all, as you might have spotted as well, those values inside the array above (KEY_ESCAPE, KEY_SHIFT etc) are actually a predefined constant of HID strokes. The values are the standards for a keyboard. And it can be predefined as below:

#define KEY_NONE                0x00
#define KEY_A                   0x04
#define KEY_B                   0x05
#define KEY_C                   0x06
.......

For the complete list please check this link.

So once you've got all that setup, we can start with the real application. But before that you need to initialize the library first.

Keypad kpd = Keypad (makeKeymap(keys), rowPins, colPins, ROWS, COLS);
And the very simplified version of the way this works is as below:
while(1){ 
if (kpd.getKeys()) // Check if there is any key being pressed, otherwise do nothing
    {
        for (int i=0; i<LIST_MAX; i++)   // If yes, then Scan the whole key list.
        {
            if ( kpd.key[i].stateChanged )   // Then only find keys that have changed state.
            {
                switch (kpd.key[i].kstate) {  // Report active key state : IDLE, PRESSED, HOLD, or RELEASED
                    case PRESSED:
                    msg = " PRESSED.";
                break;
                    case HOLD:
                    msg = " HOLD.";
                break;
                    case RELEASED:
                    msg = " RELEASED.";
                break;
                    case IDLE:
                    msg = " IDLE.";
                }
                Serial.print("Key ");
                
                // this prints the numerical order of the switch.
                Serial.print(kpd.key[i].kcode); 
                // if uncommented, this print the character, but because HID is different with ASCII you'll see strange things.
                // Will cover the HID to ASCII table translation later. 
                // Serial.print(kpd.key[i].kchar); 
                Serial.println(msg);
            }
        }
    }
}

With very simple story and example above, I hope you can fully understand the fundamental operation of the key-mapping and scanning works for this project. Again, this library tremendously help the development of this project many thanks to the contributors.

This is just the beginning part, more to come in how to handle the multi-pressed in combination with HID modifier for sequential keystrokes.


Discussions

Minh Tran wrote 07/22/2017 at 16:23 point

Hi Awesome guy!
Could you give me a Library of Cherry MX Switch,please ? Thanks you a lot !

  Are you sure? yes | no

skami wrote 09/04/2016 at 14:41 point

a very good first part! 

Just a simple question, why don't use tmk firmware with all keyboard program already make?!

  Are you sure? yes | no

Pamungkas Sumasta wrote 09/06/2016 at 11:02 point

Thanks! Good question. There are indeed several ready made, open source keyboard firmware out there, Like Soarer's, Easy AVR andf TMK as you mentioned. They do the job really well as a standard keyboard, quite efficient and have community on their back. But for this F/Society Keyboard project, I need to make sure the firmware is "Application Friendly". People can easily import their existing stuff to it to play around with the peripherals and yet the basic keyboard functionality will still work. Of course, if you're planning to make one for yourself, you can use any firmware that you want :)

  Are you sure? yes | no