Posted by
| Nick Gammon
Australia (23,120 posts) Bio
Forum Administrator |
Message
| This program is designed to easily allow for multiple simultaneous presses on a keypad matrix.
It was inspired by reading about Stenography machines which are designed to allow for high-speed typing using a special "chorded" keyboard.
Stenography machine
This is an example of how a Stenography keyboard is laid out. There are two sets of keys (initial and final) with 4 keys on the left, and 6 keys on the right (for the four fingers of each hand with you having to move the right forefinger left for the asterisk key, or the left forefinger right for the asterisk key, and the right little finger over one position for the right-hand keys (D and Z)) plus two sets of keys at the bottom for your thumbs. These are used to create vowels.
A stenographer can make a word like "Tap" by simultaneously pressing T on the left, A on the bottom, and P on the right. This combination will be printed on the paper fed through the machine, or in more modern machines interpreted by computer software.
Not all of the letters in our alphabet are present, so some letter sounds are created by pressing multiple keys, hence the name "chorded". You are playing chords by pressing keys simultaneously, like on a piano. Unlike a normal keyboard, where you usually press one key at a time, possibly with Ctrl, Alt and Shift, the stenography keyboards are designed to allow for lots of keys to be pressed at once.
Thus, to make the M sound you press P and H together. Why? You just have to memorize that. Similarly for the vowels, so E and U together would be the "I" sound ("I" as in LIT, not "I" as in RICE - that is a different combination).
The "*" key is used for modifying other keys, or for correcting errors. For example, combined with a vowel key it might produce a different vowel sound. For example, to distinguish the different sound of "A" in BAT compared to BATH.
In a stenography machine the keys are placed physically close together, so for example to press T and K together you would press down in the gap between T and K. Thus the stenographer types words by moving their fingers slightly up and down (eg. to hit T, K or the gap between T and K) and their thumbs slightly left and right (eg. to E, U or the gap between E and U).
For use with a keypad you need to install diodes if you are planning to press more than two keys at once.
See my post https://www.gammon.com.au/forum/?id=14175 which describes why those diodes are required.
In the code the rows and columns are connected up as follows, you can change that by altering the array.
const byte rowPins[ROWS] = { 8, 7, 6, 5 }; // connect to the row pinouts of the keypad
const byte colPins[COLS] = { 12, 11, 10, 9 }; // connect to the column pinouts of the keypad
When keys are pressed they are registered (remembered) as forming part of the key group. When ALL keys are released then the keys pressed during this session are then combined to make the final keypress group. EG. Press 1, then 2, then 3, then release all of them gives the group 1/2/3, regardless of the order they are pressed or released.
Thus, nothing happens when you press a key, it all happens when ALL keys are released. While at least one key is down you can add other keys, so for example 1-2-3-4 would be a valid combination. You would detect that in the switch statement further down by testing for case K1|K2|K3|K4.
This gives you considerable flexibility in choosing key combinations, with thousands of combinations possible. Since every key can be either pressed or not pressed once you start pressing keys, with a 16-key keypad there are therefore 2^16 combinations (65536) except that pressing no keys at all would not count, so there would be 65535 different combinations.
So, for example you could press and hold the "1" key and then press (in any order) 2, 5, 8, 9 to give the combination:
case K1 | K2 | K5 | K8 | K9:
// do something
break;
In my case I was using the keypad to generate moderator (and other) messages for use on my forum and Stack Exchange. For this purpose I used an Arduino Leonardo (which has a USB interface which can emulate key presses), so that pressing "C" might output, for example "Please don't use all caps".
Managing without diodes
If you don't want to go to the trouble of installing diodes, with a 4x4 (or similar) keypad, then you can still use this code. Since a key combination is not acted upon until all keys are released, then hold down the first key, and then press the others one after the other. Finally, release the first key. For example, to simulate holding down 1/2/3/4 simultaneously:
- Press and hold 1
- Press 2 (and release it)
- Press 3 (and release it)
- Press 4 (and release it)
- Release 1
Since the diodes are only required (I think) for 3 or more simultaneous keys, then this method should work.
Background
Idea based on the stenography machines, and in particular the plover software.
However all the code here is mine, it was just the ideas I used.
Keypad library: https://github.com/nickgammon/Keypad_Matrix
Keyboard library from the Arduino distribution.
Code
/*
Author: Nick Gammon
Date: 21 July 2023
This version is designed to easily allow for multiple simultaneous presses.
When keys are pressed they are registered (remembered) as forming part of the key group.
When ALL keys are released then the keys pressed during this session are then combined
to make the final keypress group. EG. Press 1, then 2, then 3, then release all of them
gives the group 1/2/3, regardless of the order they are pressed or released.
Thus, nothing happens when you press a key, it all happens when ALL keys are released.
While at least one key is down you can add other keys, so for example 1-2-3-4 would be a
valid combination. You would detect that in the switch statement further down by testing
for case K1|K2|K3|K4.
Idea based on the stenography machines, and in particular the "plover" software:
https://github.com/openstenoproject/plover
However all the code here is mine, it was just the ideas I used.
Keypad library: https://github.com/nickgammon/Keypad_Matrix
Mouse and Keyboard libraries from the Arduino distribution.
ERROR_LED_PIN (currently A1) is the pin number of an LED you have hooked up (via a resistor in series, say 220 ohms.
This lights for 300 ms if an unknown sequence is typed.
For sending text to the PC, characters are spaced apart by DELAY_BETWEEN_KEYS ms,
to give the receiver a chance to process them (currently 15 ms).
*/
#include <Keyboard.h>
#include <Keypad_Matrix.h>
const unsigned long DELAY_BETWEEN_KEYS = 15; // milliseconds
const byte ERROR_LED_PIN = A1;
// make true to type out unknown key combinations, false otherwise
#define ECHO_UNKNOWN_COMBINATION true
// make true to flash an LED if an invalid combination is pressed
#define FLASH_ERROR_LED true
// type for storing bit patterns: make uint16_t for up to 16 keys
// and uint32_t for 17 to 32 keys.
// (Using uint16_t saves a small amount of program memory).
typedef uint16_t bitMaskType;
const byte ROWS = 4;
const byte COLS = 4;
const byte TOTAL_KEYS = ROWS * COLS;
char keys[ROWS][COLS] = {
{ '1', '2', '3', 'A' },
{ '4', '5', '6', 'B' },
{ '7', '8', '9', 'C' },
{ '*', '0', '#', 'D' },
};
const byte rowPins[ROWS] = { 8, 7, 6, 5 }; // connect to the row pinouts of the keypad
const byte colPins[COLS] = { 12, 11, 10, 9 }; // connect to the column pinouts of the keypad
// A bit is assigned to each of the TOTAL_KEYS keys. If that key was pressed during a keydown/keyup
// session then that bit will be set in "combinedKeys".
const bitMaskType K0 = 1L << 0; // "0"
const bitMaskType K1 = 1L << 1; // "1"
const bitMaskType K2 = 1L << 2; // "2"
const bitMaskType K3 = 1L << 3; // "3"
const bitMaskType K4 = 1L << 4; // "4"
const bitMaskType K5 = 1L << 5; // "5"
const bitMaskType K6 = 1L << 6; // "6"
const bitMaskType K7 = 1L << 7; // "7"
const bitMaskType K8 = 1L << 8; // "8"
const bitMaskType K9 = 1L << 9; // "9"
const bitMaskType KA = 1L << 10; // "A"
const bitMaskType KB = 1L << 11; // "B"
const bitMaskType KC = 1L << 12; // "C"
const bitMaskType KD = 1L << 13; // "D"
const bitMaskType KAST = 1L << 14; // "*"
const bitMaskType KHSH = 1L << 15; // "#"
// for translating bit patterns back to key names
// these names should be the same as in the "keys" array above
// and in the same order as the bit mappings directly above
const char reverseLookup [TOTAL_KEYS] = "0123456789ABCD*#"; // same order as above bitmap
bitMaskType combinedKeys = 0; // set a bit for each key pressed
bitMaskType keysCurrentlyDown = 0; // remember which keys are currently pressed
unsigned long ledLit; // when we turned the error LED on
// helper macros to output strings with "F" macro around them
#define MSG(arg) \
do \
myKeyboard.print (F (arg)); \
while (false)
#define MSGLN(arg) \
do \
myKeyboard.println (F (arg)); \
while (false)
// do a newline easily
#define NEWLINE() \
do \
myKeyboard.println (); \
while (false)
// Create the Keypad
Keypad_Matrix kpd = Keypad_Matrix (makeKeymap (keys), rowPins, colPins, ROWS, COLS);
// class for printing to our keyboard with a delay between each key
class my_Keyboard_class : public Print
{
public:
virtual size_t write (uint8_t c);
}; // end of class my_Keyboard_class
size_t my_Keyboard_class::write (uint8_t character)
{
Keyboard.write (character);
delay (DELAY_BETWEEN_KEYS);
return 1;
} // end of my_Keyboard_class::write
my_Keyboard_class myKeyboard;
// each of the possible key codes gets mapped to a unique bit pattern
bitMaskType mapKeycodeToBitCode (const char key)
{
for (byte i = 0; i < sizeof (reverseLookup); i++)
if (key == reverseLookup [i])
return (bitMaskType) 1 << i;
return 0; // not found, shouldn't happen
} // end of mapKeycodeToBitCode
void setup ()
{
// initialize control over the keyboard:
Keyboard.begin ();
#if FLASH_ERROR_LED
// set up various pins
pinMode (ERROR_LED_PIN, OUTPUT);
#endif // FLASH_ERROR_LED
kpd.begin ();
kpd.setKeyDownHandler (keyDown);
kpd.setKeyUpHandler (keyUp);
} // end of setup
// Handle key combination once all keys are released
void processKeyCombination (const bitMaskType keys)
{
// take some action depending on what key combinations were pressed
switch (keys)
{
// some example actions
// 0
case K0:
MSGLN ("Key 0 pressed");
break;
// 1
case K1:
MSGLN ("Key 1 pressed");
break;
// A + 1
case KA | K1:
MSGLN ("Keys A and 1 pressed");
break;
// *
case KAST:
MSGLN ("Hi there!");
break;
// ---------------------------------------------------------------------
// HERE FOR NO MATCH
// ---------------------------------------------------------------------
default:
#if FLASH_ERROR_LED
// light LED to indicate an error if wanted
ledLit = millis ();
digitalWrite (ERROR_LED_PIN, HIGH);
#endif // FLASH_ERROR_LED
#if ECHO_UNKNOWN_COMBINATION
// echo what combination was pressed if wanted
MSG ("Unknown key combination: ");
for (byte i = 0; i < sizeof (reverseLookup); i++)
if (keys & (1 << i))
myKeyboard.print (reverseLookup [i]);
NEWLINE ();
#endif // ECHO_UNKNOWN_COMBINATION
break; // end of default handler
} // end of switching on which key
} // end of processKeyCombination
// remember this key was pressed
void keyDown (const char which)
{
bitMaskType bitmap = mapKeycodeToBitCode (which);
combinedKeys |= bitmap;
keysCurrentlyDown |= bitmap;
} // end of keyDown
// do action on key-up
void keyUp (const char which)
{
// this key isn't down any more
keysCurrentlyDown &= ~mapKeycodeToBitCode (which);
// do nothing if some keys still down
if (keysCurrentlyDown)
return;
// all keys have been released - process the combination
processKeyCombination (combinedKeys);
// clear the combined keys bitmap for next time
combinedKeys = 0;
} // end of keyUp
void loop ()
{
kpd.scan ();
#if FLASH_ERROR_LED
// turn LED off after 300 ms
if (ledLit)
{
if (millis () - ledLit >= 300)
{
digitalWrite (ERROR_LED_PIN, LOW);
ledLit = 0;
}
} // end of led being lit
#endif // FLASH_ERROR_LED
// do other stuff here
} // end of loop
The main loop just scans the keypad for presses (or releases) and only does something when the state of the keys changes. Once all keys are released then processKeyCombination is called so that you can decide what action to take for a particular combination of keys.
Since the keypad scanning doesn't "block" the program can be doing other things at the same time.
To allow for accidentally pressing the wrong keys, you could assign a particular key (eg. D for "duh!") to not map to any action, so that you are guaranteed that if you add D to the list of keys pressed then nothing will happen. In a stenography machine I believe that pressing the "*" key on its own was used as a backspace, to remove erroneous key combinations from the output.
Maximum number of keys
The code above allows for a maximum of 16 keys, since the bitmask for what keys are pressed is stored in a 16-bit variable (type of uint16_t). However if you wanted to make a home-made stenography machine you need at least 22 bits (from the graphic above). Some hobbyist stenography machines also split the "S" and "*" keys into two keys (the same size as the adjacent ones) so that increases the count to 24 keys. Also I've seen the number bar at the top be replaced by another key or two on the "thumb" (vowels) row. Thus you may need to allow for 25 keys.
To do that, just change the typedef for bitMaskType from uint16_t to uint32_t, and add more bit masks as appropriate (eg. where K0, K1, K2 etc. are defined). |
- Nick Gammon
www.gammon.com.au, www.mushclient.com | Top |
|