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:
- Arduino IDE
- Download and install this first, you’ll need it on the next step
- There are a ton of great examples built into the IDE for beginners to follow
- Teensyduino
- Download and install this with the Arduino IDE you just installed
- 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.