How to build your first Mechanical Keyboard

I built my first mechanical keyboard from scratch! Learn how I did it.

Numpad Firmware

When I first got to this step, I was worried that it was going to be needlessly complex: I couldn’t find any good examples and the few that I did find were very hard to follow, with little instruction. However, I discovered that there was a much better solution than writing and compiling C code, and that’s with the Arduino IDE!

Required Software

We’ll need to download and install the following software to write and upload the firmware:

  1. Arduino IDE
    1. Download and install this first, you’ll need it on the next step
    2. There are a ton of great examples built into the IDE for beginners to follow
  2. Teensyduino
    1. Download and install this with the Arduino IDE you just installed
    2. This is a lifesaver! It allows us to compile Arduino code and upload to Teensy

And that’s it, nothing crazy required. Once you get our USB cable plugged in we’re ready to go.

The Firmware Code

Below is the firmware code, in full. The formatting is a little bit goofy in this post, but it’ll look nicer in the GitHub.

//Pin setup
int ledPin = PIN_D6;
bool ledStatus = HIGH;

//C0->C3, R0->R4
int columnPins[] = { PIN_B0, PIN_B1, PIN_B2, PIN_B3 };
int rowPins[] = { PIN_D1, PIN_D2, PIN_D3, PIN_C6, PIN_C7 };
int columnCount = sizeof(columnPins)/sizeof(int);
int rowCount = sizeof(rowPins)/sizeof(int);

//How quickly to poll each key, in ms
int pollDelay = 1000/30;
int columnPollTime = 10; //I don't know what a good number is

//What are the keys in the numpad
//Left to right, top to bottom C0->C3, R0->R4
int columns[][5] = { 
 { KEY_NUM_LOCK, KEYPAD_7, KEYPAD_4, KEYPAD_1, KEYPAD_0 },
 { KEYPAD_SLASH, KEYPAD_8, KEYPAD_5, KEYPAD_2, 0},
 { KEYPAD_ASTERIX, KEYPAD_9, KEYPAD_6, KEYPAD_3, KEYPAD_PERIOD },
 { KEYPAD_MINUS, 0, KEYPAD_PLUS, 0, KEYPAD_ENTER }
 };

void setup() {

//Initialize I/O
 for(int columnIndex = 0; columnIndex < columnCount; ++columnIndex){
 //Set the pin and turn it off
 int columnPin = columnPins[columnIndex];
 pinMode(columnPin, INPUT_PULLUP);
 }
 for(int rowIndex = 0; rowIndex < rowCount; ++rowIndex){
 //Set the pin
 int rowPin = rowPins[rowIndex];
 pinMode(rowPin, OUTPUT);
 digitalWrite(rowPin, HIGH);
 }

 //Initialize the keyboard
 Keyboard.begin();
 Serial.begin(9600);
}




void loop() {

//Poll all the rows
 pollRows();

 //We've pressed all the keys, or as many as we can
 //Send the message
 Keyboard.send_now();

 //Release all the keys
 //Keyboard.releaseAll();

//Toggle the LED
 digitalWrite(ledPin, ledStatus);
 ledStatus = !ledStatus;

//Wait for the next poll
 delay(pollDelay);

//Pause for debug
 //delay(5000);
}

void pollRows(){
 //For each row
 for(int rowIndex = 0; rowIndex < rowCount; ++rowIndex){
 //Drive the row high
 int rowPin = rowPins[rowIndex];
 digitalWrite(rowPin, LOW);

//Wait for things to settle(?)
 //delay(columnPollTime);

//Now read each row
 pollColumns(rowIndex);

//Turn off the pin!
 digitalWrite(rowPin, HIGH);
 }
}

void pollColumns(int rowIndex){
 //Read all the columns and get the keys that are pressed
 for(int columnIndex = 0; columnIndex < columnCount; ++columnIndex){
 //Is it high?
 int columnPin = columnPins[columnIndex];
 bool activated = !digitalRead(columnPin);
 int key = columns[columnIndex][rowIndex];
 if(key != 0){
 if(activated){
 //We need to get that character for the numpad
 //Serial.println(key);
 Keyboard.press(key);
 }else{
 //Don't press that key
 Keyboard.release(key);
 }
 }
 }
}

Let’s break this down into bite sized chunks.

Initialize Pins and Timing

//Pin setup
int ledPin = PIN_D6;
bool ledStatus = HIGH;

//C0->C3, R0->R4
int columnPins[] = { PIN_B0, PIN_B1, PIN_B2, PIN_B3 };
int rowPins[] = { PIN_D1, PIN_D2, PIN_D3, PIN_C6, PIN_C7 };
int columnCount = sizeof(columnPins)/sizeof(int);
int rowCount = sizeof(rowPins)/sizeof(int);

//How quickly to poll each key, in ms
int pollDelay = 1000/30;

The sketch starts out by setting up the pins, including the LED pin (which we will flash later). You can see we also define our column and row pin arrays, and figure out how many rows and columns we have to iterate over. We then determine how quickly we should poll, in this example it’s 30 times a second. The pin names are collected from the pinout PDF and placed into our pin lists.

Mapping the Keypresses

//What are the keys in the numpad
//Left to right, top to bottom C0->C3, R0->R4
int columns[][5] = { 
 { KEY_NUM_LOCK, KEYPAD_7, KEYPAD_4, KEYPAD_1, KEYPAD_0 },
 { KEYPAD_SLASH, KEYPAD_8, KEYPAD_5, KEYPAD_2, 0},
 { KEYPAD_ASTERIX, KEYPAD_9, KEYPAD_6, KEYPAD_3, KEYPAD_PERIOD },
 { KEYPAD_MINUS, 0, KEYPAD_PLUS, 0, KEYPAD_ENTER }
 };

We need to map out which key codes are associated with which row/column. This is identical to the circuits we drew before. For an instance where there is no switch, we simply place a 0 – we won’t use it anyways.

Setting up the Pins

void setup() {

//Initialize I/O
 for(int columnIndex = 0; columnIndex < columnCount; ++columnIndex){
 //Set the pin and turn it off
 int columnPin = columnPins[columnIndex];
 pinMode(columnPin, INPUT_PULLUP);
 }
 for(int rowIndex = 0; rowIndex < rowCount; ++rowIndex){
 //Set the pin
 int rowPin = rowPins[rowIndex];
 pinMode(rowPin, OUTPUT);
 digitalWrite(rowPin, HIGH);
 }
 
 //Initialize the keyboard
 Keyboard.begin();
 Serial.begin(9600);
}

We may have defined which pins to use, but we haven’t told them whether they should be input or output. Due to the nature of our switch circuit, we are going to set each row HIGH (or 1), and then turn them LOW (or 0) one at a time to poll. Therefore, we loop over all of the rows and set them to OUTPUT pins. Likewise, we’ll be reading the values from the columns, so they will be set to INPUT_PULLUP, so we utilize the Teensy’s internal pullup resistor to drive them HIGH. Finally, we enable the Keyboard and Serial libraries to vastly simplify the code.

The Main Loop

void loop() {

//Poll all the rows
 pollRows();
 
 //We've pressed all the keys, or as many as we can
 //Send the message
 Keyboard.send_now();

//Toggle the LED
 digitalWrite(ledPin, ledStatus);
 ledStatus = !ledStatus;

//Wait for the next poll
 delay(pollDelay);
}

This is our main code loop, made up of a few functions. First, we poll all the rows to see which switches were pressed. We then send all of the keys to the operating system using the Keyboard library. Finally, we toggle the LED so we know things are running, and wait for the poll delay before we check the keys again.

Polling the Rows

void pollRows(){
 //For each row
 for(int rowIndex = 0; rowIndex < rowCount; ++rowIndex){
 //Drive the row low
 int rowPin = rowPins[rowIndex];
 digitalWrite(rowPin, LOW);

//Wait for things to settle(?)
 //delay(columnPollTime);

//Now read each row
 pollColumns(rowIndex);

//Turn off the pin!
 digitalWrite(rowPin, HIGH);
 }
}

Here is how we set up each row for polling. We start by iterating over every row pin in the array, and write it LOW so we can potentially read the signal on the column. We then poll each column for it’s status, and reset the row back to the HIGH state so we can read the next.

Polling the Columns

void pollColumns(int rowIndex){
 //Read all the columns and get the keys that are pressed
 for(int columnIndex = 0; columnIndex < columnCount; ++columnIndex){
 //Is it high?
 int columnPin = columnPins[columnIndex];
 bool activated = !digitalRead(columnPin);
 int key = columns[columnIndex][rowIndex];
 if(key != 0){
 if(activated){
 //We need to get that character for the numpad
 //Serial.println(key);
 Keyboard.press(key);
 }else{
 //Don't press that key
 Keyboard.release(key);
 }
 }
 }

Here’s where the action is. We just set a particular row to be polled, so next we iterate over each column and check the status. Since the row was set to LOW, and the diodes are all pointing cathode side towards the row, that means the input value will no longer be HIGH if the switch is pressed. Remember, the pullup resistor means the pin will be HIGH if it is not connected. The diodes also help isolate each signal so we don’t worry about confusing the HIGH of not being connected with the HIGH of a row we aren’t polling.

If the value of a column is LOW, we then look up the character code from the table defined at the start of the code and send it to the keyboard. If the key isn’t being pressed, we release it. The press and release signals are prepared, but not fully sent until later in the main loop.

Test your Firmware

You should be ready to go! Plug in your keyboard, upload the Sketch, and open up your favorite text editor and try it out! If this is your first build, or you are otherwise new to project like this, you might have a few issues but that’s okay. If you can get some keystrokes but not all, double check your solder joints and your circuit to make sure everything is in it’s place. This is where careful labeling is important! Hopefully, without needing too many fixes, you’ll have a fully functional keyboard that’s ready to be fully assembled.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s