This has a major issue however. Each Keyboard.write() call generates a press/release cycle. If you keep a button pushed, instead of a single, long key press, the computer will receive a ton of press/release events. We need to keep the buttons states between loop() calls.

Adding memory to the keyboard

Here’s a second attempt, with two modifications. First, to ease the addition/removal of a button, the code uses arrays instead of doing all steps four times. Second thing changed: each button now remember its state.

// Number of buttons to handleconstintbuttonsCount=4;// Arduino PINs to useconstintpins[buttonsCount]={2,3,4,5};// Keys to send (order has to match the pins array)constbytekeys[buttonsCount]={KEY_UP_ARROW,KEY_DOWN_ARROW,KEY_LEFT_ARROW,KEY_RIGHT_ARROW};boolstatus[buttonsCount]={LOW};voidsetup(){for(inti=0;i<buttonsCount;++i){pinMode(pins[i],INPUT);}Keyboard.begin();}voidloop(){for(inti=0;i<buttonsCount;++i){constintpinStatus=digitalRead(pins[i]);if(pinStatus!=status[i]){status[i]=pinStatus;if(pinStatus==HIGH){Keyboard.press(keys[i]);}else{Keyboard.release(keys[i]);}}}}

So… the keyboard now remembers which buttons are pressed, and should generate a single couple of events for each button press/release. Should. There’s still an issue: mechanical buttons are not perfect. Many events are still generated. This is due to a phenomenon called bounce.

Debouncing the keyboard

A simple way to debounce a button is, well, really simple: ignore all changes to the state of the button during a short delay after an initial change. While it’s not the most precise way and could be problematic in a more complex scenario, it’s perfectly fine to do this for a keyboard, given we keep this delay short enough.

Let’s throw in an array to remember the last event acknowledged by the keyboard:

// Number of buttons to handleconstintbuttonsCount=4;// Arduino PINs to useconstintpins[buttonsCount]={2,3,4,5};// Keys to send (order has to match the pins array)constbytekeys[buttonsCount]={KEY_UP_ARROW,KEY_DOWN_ARROW,KEY_LEFT_ARROW,KEY_RIGHT_ARROW};// Debounce delayconstlongdebounceDelay=50;boolstatus[buttonsCount]={LOW};longlastDebounces[buttonsCount]={0};voidsetup(){for(inti=0;i<buttonsCount;++i){pinMode(pins[i],INPUT);}Keyboard.begin();}voidloop(){for(inti=0;i<buttonsCount;++i){constintpinStatus=digitalRead(pins[i]);if(pinStatus!=status[i]&&millis()-debounceDelay>lastDebounces[i]){status[i]=pinStatus;if(pinStatus==HIGH){Keyboard.press(keys[i]);}else{Keyboard.release(keys[i]);}lastDebounces[buttonNumber]=millis();}}}

You’ll maybe need to adjust the debounce delay according to your buttons. Try to keep it as short as possible.

Conclusion

And voilà! We now have a fully functional keyboard, to which it’s easy to add/remove/change buttons. There’s still room for improvement: it would be easy to allow it to send key sequences instead of single key presses, for example.