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);
}
6
  • If you don't use swiActR take it out of the struct. In current code, {sw0, 'R', KEY_LEFT_CTRL} inits bounceEnd equal to sw0, swiNum to R, and swiActP to KEY_LEFT_CTRL. Initializers have to be in the right order, ie have to match the order declared in struct. Values can be left off at the end, to default to 0, but can't be left off the front Commented Nov 16, 2015 at 19:51
  • Ah, I see that, but even if I use {0, sw0, 'z', KEY_LEFT_CTRL}, it appears I still get declaration errors in void doAction() like "'tosend' was not declared in this scope" and a few others
    – sylcat
    Commented Nov 16, 2015 at 20:25
  • Both tosend and b aren't declared in the scope of doAction. Just change char action, char chmodi to char tosend, char chmodi and delete the two lines with b-> at front. In doButton add b->charMod to the first doAction call. Delete else { doAction(b->swiNum, 'R', b->swiActR); if you don't plan to do an action for button releases, else perhaps fix the code up with a modifier for the release action char. Just make the function calls and function defs match up, only use vars that are in scope, etc Commented Nov 16, 2015 at 20:50
  • Woot! That compiled! It was throwing each button like zP, AP, BP, etc but I just had to change the doAction to doAction(b->swiNum, 'P', b->swiActP, b->charMod); to order it correctly. Awesome! I've updated the compiled code. It looks to be swiActR that is unused. Before cleaning that I just wanted to ask, is the current code handling key releases correctly on single characters without Keyboard.releaseAll(); after Keyboard.write(tosend); ? It's acting very well but I was under the impression swiActR was used for release before. Thanks so much! Checkmark+upvote on the way after you read this!
    – sylcat
    Commented Nov 16, 2015 at 21:13
  • In some early code, swiActR held a character to be sent when a button on the arduino was released. It wasn't related to sending a key-up to the PC. IOW, Keyboard.write sends a key press and then a key release, so it was taking care of the PC side Commented Nov 16, 2015 at 23:35

1 Answer 1

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.

7
  • I'm afraid this doesn't help. Using {0, sw0, '\032'} or the '\x1A' reports nothing when the button is pressed. I have also previously tried the method of adding another action but it still leaves me with no clear syntax when trying to report a modifier like KEY_LEFT_CTRL in the struct. It never registers a ctrl at all much less combines it with 'z'..
    – sylcat
    Commented Nov 12, 2015 at 22:17
  • This post is mistaken as it overlooks the fundamental fact that keyboards don't send ASCII codes - rather they send scan codes. Commented Nov 13, 2015 at 1:49
  • 1
    @ChrisStratton, yes, physical keyboards send scan codes, but the Arduino Keyboard library API accepts (for most calls) ASCII codes, not scan codes. It also accepts some modifier-key codes like I mentioned in an answer-edit a few minutes ago Commented Nov 13, 2015 at 2:48
  • I apologise, I haven't made something clear. THIS is what I don't know how to change ---> {0, sw0, 'A'} I haven't used a setup like that before. All these methods and I still need to know what goes in here---> {0, sw0, 'A'} to get new output. I need some kind of alternate example for that bit of code to understand why I'm seeing the strange output I'm seeing. The only thing that ever works in there is 'A' or a single character. If I use this setup without knowing how to change it I'm going to try something like {0, sw0, actionChar1} or worse and It's going to fail again and again.
    – sylcat
    Commented Nov 13, 2015 at 20:47
  • @sylcat, see edit Commented Nov 14, 2015 at 6:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.