3

I have a set of buttons wired to an arduino leonardo that are meant to send either single keystrokes to a computer "a, b, c..." etc, or a key sequence like ctrl+z (to perform an undo command.) Effectively this is a little usb keyboard with pre defined buttons. The way the struct is now, I can only manage to send one keystroke, never a modifier key+letter like I need.

The full code is listed here under "working code": Arduino sending keystrokes via push-buttons. Proper bouncing and manually setting buttons?

It just debounces the buttons and sends back actions (just single letters right now) using the ones defined in the struct. (thanks very much jwpat7)

The part in question is this

struct ButtonStruct buttons[nSwitches] = {
  {0, sw0, 'A'},
  {0, sw1, 'B'}, 
  {0, sw2, 'C'}, 
  {0, sw3, 'D'},
  {0, sw4, 'E'},
  {0, sw5, 'F'},
  {0, sw6, 'G'},
  {0, sw7, 'H'}};

I effectively want to replace the 'A' in {0, sw0, 'A'} to something that sends ctrl+z keystroke/keysequence instead of the letter 'A'.

I've tried KEY_LEFT_CTRL, setting a variable like char ctrlKey = KEY_LEFT_CTRL; and trying {0, sw0, ctrlKey, 'z'}, the "substitution" hex here: http://www.nthelp.com/ascii.htm and a number of other things.

I've read through a lot and I can't seem to find anything relevant. I've been told it's possible to use pointers to create a key-sequence but a -> operand in there just throws errors. I'm new and I realize I might have a core principal wrong, but isn't there some simple syntax to send a ctrl+z command (and then releasing it obviously) using that space? When I put {0, sw0, KEY_LEFT_CTRL, 'z'} it just reports "Lz", using the last letter in KEY_LEFT_CTRL instead of treating it as a modifier. I'm so confused

(Also right now one press = one character and doesn't continually report like holding down a key on a regular keyboard. I'd like to fix that too but I'd settle for just a bloody ctrl+z at this point.)

EDIT: Current Code (compiled!)

    enum { sw0=2, sw1=3, sw2=4, sw3=5, sw4=6, sw5=7, sw6=8, sw7=9}; // Switchbutton lines
enum { nSwitches=8, bounceMillis=42}; // # of switches; debounce delay
struct ButtonStruct {
  unsigned long int bounceEnd;  // Debouncing-flag and end-time
  // Switch number, press-action, release-action, and prev reading
  byte swiNum, swiActP, charMod, swiActR, swiPrev;
};

struct ButtonStruct buttons[nSwitches] = {
  {0, sw0, 'z',  KEY_LEFT_CTRL}, 
  {0, sw1, 'B'}, 
  {0, sw2, 'C'}, 
  {0, sw3, 'D'},
  {0, sw4, 'E'},
  {0, sw5, 'F'},
  {0, sw6, 'G'},
  {0, sw7, 'H'}};
//--------------------------------------------------------
void setup() {
  for (int i=0; i<nSwitches; ++i)
    pinMode(buttons[i].swiNum, INPUT_PULLUP);
Keyboard.begin();
}
//--------------------------------------------------------
byte readSwitch (byte swiNum) {
  // Following inverts the pin reading (assumes pulldown = pressed)
  return 1 - digitalRead(swiNum);
}
//--------------------------------------------------------
void doAction(byte swin, char code, char tosend, char chmodi) {

if (chmodi) {    // See if modifier needed
   Keyboard.press(chmodi);
   Keyboard.press(tosend);
   delay(100);
   Keyboard.releaseAll();
} else {
   Keyboard.write(tosend);
}
}
//--------------------------------------------------------
void doButton(byte bn) {
  struct ButtonStruct *b = buttons + bn;
  if (b->bounceEnd) { // Was button changing?
    // It was changing, see if debounce time is done.
      if (b->bounceEnd < millis()) {
    b->bounceEnd = 0;    // Debounce time is done, so clear flag
    // See if the change was real, or a glitch
    if (readSwitch(b->swiNum) == b->swiPrev) {
      // Current reading is same as trigger reading, so do it
      if (b->swiPrev) {
        doAction(b->swiNum, 'P', b->swiActP, b->charMod);
      } 
    }
      }
  } else {  // It wasn't changing; but see if it's changing now
    if (b->swiPrev != readSwitch(b->swiNum)) {
      b->swiPrev = readSwitch(b->swiNum);
      b->bounceEnd = millis()+bounceMillis; // Set the Debounce flag
    }
  }
}
//--------------------------------------------------------
long int seconds, prevSec=0;
void loop() {
  for (int i=0; i<nSwitches; ++i)
    doButton(i);
}
sylcat
  • 65
  • 6

1 Answers1

2

First off, consider trying '\032' or '\x1A' for the ctrl-z. (See, eg, How to send ctrl+z in C on stackoverflow.)

To send a ctrl-z, use something like:

Keyboard.press(ctrlKey);
Keyboard.press('z');
delay(100);
Keyboard.releaseAll();

For more examples of using modifier keys, see the KeyboardPress page at arduino.cc, and the example program KeyboardLogout.ino. [To coordinate this with the struct data structure approach, you could use code somewhat like

tosend = b->actionChar1;
if (tosend < ' ') {    // See if modifier needed
   Keyboard.press(ctrlKey);
   Keyboard.press(tosend+96); // or is it 64?
   delay(100);
   Keyboard.releaseAll();
} else {
   Keyboard.write(tosend);
} 

[You'll need to test which offset, 96 or 64, works in the above code; I don't have a Leonardo or similar to run this on, and haven't looked at the Keyboard library source to see how it treats this.] Another way is to add a modifier-byte field in the struct, and issue it if its non-zero.]

Second, suppose the current form of ButtonStruct is analogous to

struct ButtonStruct {
  unsigned long int bounceEnd;  // Debouncing-flag and end-time
  // Switch number, press-action and prev reading
  byte swiNum, actionChar1, swiPrev;
};

where actionChar1 is the action character in use now. You could add another byte to the struct, say actionChar2, and after a statement like Keyboard.write(b->actionChar1); add the following:

if (b->actionChar2)
  Keyboard.write(b->actionChar2);

which says to write out actionChar2 if it is non-zero.

In some of the questions linked to the previous question, you may see advice that Keyboard.write won't send non-printing characters. I don't know if that's so, but in any case, there are mods available for Keyboard (ie, changes to the library) that allow non-printing characters and special characters to be sent.

Also note, if your program gets close to using all the RAM memory, you may want to keep buttons[] in ProgMem and access it from there so it doesn't need to be copied into RAM. (See the three ProgMem questions that are linked in the top right sidebar . . .) If you change the struct to use strings instead of single chars for its actions, using less RAM will increase in importance.

Edit re: “what I don't know how to change ---> {0, sw0, 'A'}”:

{0, sw0, 'A'} initializes an element of buttons[]. In buttons[], each element is a ButtonStruct, because buttons[] is an array of ButtonStructs. (For more about structs, see syntax and examples in eg Struct (C programming language) in wikipedia.)

You control what information can be in each element of buttons[] by modifying the definition of ButtonStruct. If you need more data items in each buttons[] entry, then add more data items in the definition of ButtonStruct. Likewise for fewer.

After you decide what information you need to have available to process a button press, modify ButtonStruct to contain exactly that information. It may be that some information will apply in some cases but not in all. If so, when you initialize buttons[], have some value (say 0) that stands for not-used, and fill in not-used spots with that value.

For example, if you need key modifiers (shift, ctrl, alt) add a modifier item to ButtonStruct.

Whenever you change ButtonStruct, you need to change buttons[] initialization to match. For example, if ButtonStruct were to include Keyboard Modifiers and looked like

struct ButtonStruct {
  // Switch number, press-actions and prev reading
  byte swiNum, actionChar, charMod, swiPrev;
  unsigned long int bounceEnd;  // Debouncing-flag and end-time
};

[with some items in a different order than before!] your initializer elements in the buttons[] declaration could look like

  { sw29, 'Q' },

where charMod, swiPrev, and bounceEnd are left to default to zero, or like

  { sw37, 'R',  KEY_LEFT_CTRL},

where swiPrev and bounceEnd default to zero.

Then, in your button-action code you could say:

tosend = b->actionChar;
chmodi = b->charMod;
if (chmodi) {    // See if modifier needed
   Keyboard.press(chmodi);
   Keyboard.press(tosend);
   delay(100);
   Keyboard.releaseAll();
} else {
   Keyboard.write(tosend);
}

For those elements of buttons[] where you specified a modifier, the if's first branch executes, and for those where you didn't, ie, for plain unmodified characters, the second one does.

There are all sorts of variants on these methods. For example, instead of specifying action characters and modifiers in the buttons[] array, you could change it so that you specify function names, or could have several classes of actions. If 80% of the actions were to issue one simple character, make that class 0, so the action class can usually default to 0.

James Waldby - jwpat7
  • 8,920
  • 3
  • 21
  • 33