Go Down

Topic: Keypad .kstate question (Read 716 times) previous topic - next topic

bsohn

#15
May 16, 2018, 05:11 am Last Edit: May 16, 2018, 05:15 am by bsohn
I apologize for how blunt I am about to be but I need to figure out your level of programming knowledge before I start explaining really basic things like scope, array indexing, and objects.

The reason I say this is because you are using
Code: [Select]
encodermatrix.key[6].kstate
completely incorrectly. 
encodermatrix.key[indexNumber].kstate is only used when you are expecting several 'encoders' to be pressing their associated buttons "at the same time".
So if I understand you correctly your encoder is something like a wheel with a bump on it that first presses and releases one key and then some short time later the bump presses and releases a second key?  Are you doing this so you know which direction the encoder is turning?
I also need to point out that in order to generate any kind of code (grey code in your case) requires multiple bits to be high or low at the same time which directly implies that you should be using getKeys().

This is just pure luck.  What's happening in reality is that you are only registering one button pressing at a time when you see this output.
The key list always starts storing active keys at index zero.  So when your encoder is going CW button 2 is actually being recorded in the key list at index 0 and when it reverses in the CCW direction button 1 is being recorded in the key list index 0 position.  It looks good for this example of your output but it's not working the way you think it is.  That is why you can't get keys 3 and higher to work.

I'm off to bed for the evening so to get an idea of what I am talking about take a look at two example sketches.

File --> Examples --> Keypad --> EventKeypad   :   for when you are reading single keys at a time and,
File --> Examples --> Keypad --> Multikey           :   for when you need to read multiple keys at a time.
OK I am pretty novice at programming, but once I find out exactly the way something works I can usually logically figure it out and then find examples of such to get things to work and figure it out fairly quickly.. Your explanation of how kstate actually works indicates that my thought process on how kstate worked is flawed as I thought by what I was reading was flawed and after such I was trying to figure out what was wrong.

In reality I am basically trying to replicate the Grey Code that is output by the Encoder which can be replicated on button presses when the encoder is turning rather than by actual pinstates. This is so that I can have Multiple encoders in a Matrix rather than using up a ton of inputs on the processor for those encoders.. ie 6 encoders would use 12 wires + a ground normally. Where in a 4x3 matrix it would only need 7.

So with what you are saying about kstate and the dumb luck on finding something that worked.. is it possible to limit the list of the multi key to ONLY look at say keys 3 and 4 or 7 and 8 at any given time based on the initial key that was received through geyKey that determined that the encoder had started turning. What I am afraid of with just reading the Keys from the multi key code is the possible loss of synchronization between the Key pairs that would create the Virtual Grey code based on the button states to determine direction.

I could probably jerry rig this code to work with any pairs as I do know the initial turn and button used by turning all the encoders to be at the 0, 1 positions since more than likely encoders will only be turned at one time. But I know that won't be "proper code" and it could cause oddities later.

The encoders that I have are full step encoders that put out the following signals on each index (output A, output B) of (0,0) (0,1) (1,1) and (1,0).. and the virtual grey code I would hopefully be able to track these presses and then be able to process the pairs to figure out the direction of turn and then have a single output command for the direction that the encoder is being turned.

bsohn

OK after sleeping on it and doing some more reading and trying to decipher the .cpp with my limited knowledge can you let me know if I have the way getKeys works correct..

say I am turning 1 encoder which puts out 4 pulses in the rotating A and B Key press. and say A is the first press

In program

loop 1 the getKeys scan would place button A into the Keys Array in position 0 because it activated.
loop 2 the getKeys scan would  add button B to the array in position 1 because it activated while A is still active.
loop 3 button A turns off so he getKeys scan would remove button A from position 0 but leave button B in the array because it is still activated.
loop 4 Both buttons are off so the Active Keys array would be clear

and now say I turn two encoders at the same exact time in the same direction which would be virtually impossible to time them the same am I correct that this would happen: Iwill use A, B, C, and D. AB being one, CD being the other.

Loop 1 - Active A, C into positions 0, 1
Loop 2 - Active A, C remain in positions 0, 1, B, D into positions 2, 3
Loop 3 - Active B, D remain in positions 2, 3
Loop 4 - Everything would clear if nothing was active

When a button is placed in the active array is it possible to call on the state of that button directly by the .char? or is the state call based on the array position?

Since if my understanding is correct on the getKeys and I can call directly then I should be able to still do a comparison of two specific buttons and their current state to cause an action ONLY when both buttons meet a certain requirement i.e. button A on Button B off, or Button A on and Button B on.. which is what I need to be able to do to get the virtual button press greycode so I can determine direction and output.

I had another though but dismissed it, of possible overlapping keypads one for Even and one for Odd but I think that could cause hardware issues depending on what buttons were pressed due to the overlapping columns and the switching pin states. This Idea was so that I could track the odd and even completely independently.

Anyway if you can let me know if my understanding of how it works is on track and if there is a way to look directly at specific buttons as to their activity and state using this I think I will be able to figure out something that works correctly and isn't pure luck.

mstanley


Quote
and now say I turn two encoders at the same exact time in the same direction which would be virtually impossible to time them the same am I correct that this would happen: Iwill use A, B, C, and D. AB being one, CD being the other.

Loop 1 - Active A, C into positions 0, 1
Loop 2 - Active A, C remain in positions 0, 1, B, D into positions 2, 3
Loop 3 - Active B, D remain in positions 2, 3
Loop 4 - Everything would clear if nothing was active
You sir have just made my day!  Yes, that is exactly how it is operating.  Good job on your sleeping skills. ;)


Quote
When a button is placed in the active array is it possible to call on the state of that button directly by the .char? or is the state call based on the array position?
OK, to continue with your example above, with 4 keys being pressed at different times you could actually have a new keypress fill position 0.  Ex. if A goes to position 0 and B goes to position 1, and after a few moments A clears so that all you have on the list is B in position 1, then the next keypress (let's say D) will go to position 0.  That means you need to search the list to find the location of any given key.  They are only guaranteed to stay in the same position until they have gone through the complete set of states.  PRESSED --> [maybe HOLD] --> RELEASED --> IDLE and then that key is removed from the list making that position available for use by another key.

Now that I've talked about the problem let's see if there is a solution.  The key list is actually a list of C++ key objects.  (See Key.h in the keypad/utility folder.)  Each key object has 4 attributes:  key.kchar, key.kcode, key.kstate, and key.stateChanged.  Any time any key.kstate changes key.stateChanged is made true and keypadEventListener is called in the user's sketch.

So each key on the list has 4 attributes and you can read them any time that a key is on the list.  You could use one of the two findInList() functions provided by the keypad library to locate the key within the list.  Note: kcode is an automatically assigned number for each key listed in the keymap in the user's sketch.
Code: [Select]
// Search by character for a key in the list of active keys.
// Returns -1 if the key is NOT found or returns the index into the list of active keys.
int Keypad::findInList (char keyChar) {
  for (byte i=0; i<LIST_MAX; i++) {
    if (key[i].kchar == keyChar) {
      return i;
    }
  }
  return -1;
}

// Search by code for a key in the list of active keys.
// Returns -1 if the key is NOT found or returns the index into the list of active keys.
int Keypad::findInList (int keyCode) {
  for (byte i=0; i<LIST_MAX; i++) {
    if (key[i].kcode == keyCode) {
      return i;
    }
  }
  return -1;
}

Or you could process the list (read the 4 attributes) in your own code.  The thing to understand is that each time getKeys() is called (polled by the loop() function) it updates the list if any changes were found.  If anything changed then keypadEventListener() is called.  This means you could record and compare millis() between two events.  Ex. if you see that the state of A changed, stateChanged==true, then you could record the time A_pressed=millis() and A_released=millis(), etc.  That would let you compare which events occured at a given time(s).

To answer your other question about multiple keypads, yes you can.  Over time I was able to make sure that each keypad runs completely separate from one another.  You would just create separete kpd1 and kpd2 and then run both kpd1.getKeys() and kpd2.getKeys() in the loop().

bsohn

#18
May 16, 2018, 08:06 pm Last Edit: May 16, 2018, 08:16 pm by bsohn
You sir have just made my day!  Yes, that is exactly how it is operating.  Good job on your sleeping skills. ;)


OK, to continue with your example above, with 4 keys being pressed at different times you could actually have a new keypress fill position 0.  Ex. if A goes to position 0 and B goes to position 1, and after a few moments A clears so that all you have on the list is B in position 1, then the next keypress (let's say D) will go to position 0.  That means you need to search the list to find the location of any given key.  They are only guaranteed to stay in the same position until they have gone through the complete set of states.  PRESSED --> [maybe HOLD] --> RELEASED --> IDLE and then that key is removed from the list making that position available for use by another key.

Now that I've talked about the problem let's see if there is a solution.  The key list is actually a list of C++ key objects.  (See Key.h in the keypad/utility folder.)  Each key object has 4 attributes:  key.kchar, key.kcode, key.kstate, and key.stateChanged.  Any time any key.kstate changes key.stateChanged is made true and keypadEventListener is called in the user's sketch.

So each key on the list has 4 attributes and you can read them any time that a key is on the list.  You could use one of the two findInList() functions provided by the keypad library to locate the key within the list.  Note: kcode is an automatically assigned number for each key listed in the keymap in the user's sketch.
Code: [Select]
// Search by character for a key in the list of active keys.
// Returns -1 if the key is NOT found or returns the index into the list of active keys.
int Keypad::findInList (char keyChar) {
  for (byte i=0; i<LIST_MAX; i++) {
    if (key[i].kchar == keyChar) {
      return i;
    }
  }
  return -1;
}

// Search by code for a key in the list of active keys.
// Returns -1 if the key is NOT found or returns the index into the list of active keys.
int Keypad::findInList (int keyCode) {
  for (byte i=0; i<LIST_MAX; i++) {
    if (key[i].kcode == keyCode) {
      return i;
    }
  }
  return -1;
}

Or you could process the list (read the 4 attributes) in your own code.  The thing to understand is that each time getKeys() is called (polled by the loop() function) it updates the list if any changes were found.  If anything changed then keypadEventListener() is called.  This means you could record and compare millis() between two events.  Ex. if you see that the state of A changed, stateChanged==true, then you could record the time A_pressed=millis() and A_released=millis(), etc.  That would let you compare which events occured at a given time(s).

To answer your other question about multiple keypads, yes you can.  Over time I was able to make sure that each keypad runs completely separate from one another.  You would just create separete kpd1 and kpd2 and then run both kpd1.getKeys() and kpd2.getKeys() in the loop().
OK yes I figured out the issue of the key actually taking the earlier place in the list but I was too lazy to write that much.. lol..

So just to clarify in the line "int Keypad::findInList (char keyChar)" if I was to search by the character then "char keyChar" could be the stated variable of the key I want to find the state of such as Key "1" or Key "A" or pretty much anything as long as it was the Character that was defined in the matrix.. Am I correct here.. If so I am sure I can make something work how I want now knowing how things work correctly..

As far as the Dual Keypads with arrays I knew that was possible and actually be using that in the overall program but what I was mentioning was I wasn't sure about overlapping keypads.. ie on keypad Matrix running off of rows dedicated to pins 1, 3 and columns on 5 and 6 and then another with rows 2, 4, and columns on 5, 6.. Basically creating a 4x2 matrix but is read as two separate 2x2 matrix's but I am thinking that depending on the pin state for the columns things may or may not interfere at some point  so for safety I am thinking the previous list lookup would be the WAY better route to go..

I actually have a full working program that I made with the KeyPad and the Buxtronix encoder reading but the encoders in single method take up SO many digital pins that it would limit future ideas so that is what I am working on trying to get a Matrix based encoder set-up working.

I thank you very much for clarification on these things as I now think i have the correct direction to get things working the way I expect them to work.

bsohn

OK So now here is where my definite novice ness comes out. I cannot for the life of me figure out how to use.

Code: [Select]


// Search by character for a key in the list of active keys.
// Returns -1 if the key is NOT found or returns the index into the list of active keys.
int Keypad::findInList (char keyChar) {
  for (byte i=0; i<LIST_MAX; i++) {
    if (key[i].kchar == keyChar) {
      return i;
    }
  }
  return -1;
}



from the .cpp

So just so I get an understanding of how to use this how would I use this to simply make a query to the list for say button "2".

From what I understand with this I should be able to just see if button "2" is in the list and it would return "2" if it is there and "-1" if it is not..

Please correct me if I am wrong as well as how use it.

Thank you

mstanley

From what I understand with this I should be able to just see if button "2" is in the list and it would return "2" if it is there and "-1" if it is not..
It's a two step process and the first step is to call findInList():
Code: [Select]
int position_in_list;

position_in_list = findInList('2');  // Searches to find list index of button '2'.

So position_in_list will now contain a number between 0 and 9 because the list can hold 10 active keys.

Now you can use the list:
Code: [Select]
char keyChar;
int keyCode;
KeyState keyState;
boolean keyIsActive;

keyChar = myKeypad.key[position_in_list].kchar;            // Name of the key you are interested in.
keyCode = myKeypad.key[position_in_list].kcode;            // Position in the keymap array.
keyState = myKeypad.key[position_in_list].kstate;          // Most recent state. Pressed, Idle, etc.
keyIsActive = myKeypad.key[position_in_list].stateChanged; // true if this key just changed.

stateChanged is important so you can tell which key, or keys, in the list actually had a change.  If you are pressing 7 different keys all at the same time but you only released 2 of them, then the list will contain 2 active keys that recently changed state. 

Be sure you only read from the list.

bsohn

It's a two step process and the first step is to call findInList():
Code: [Select]
int position_in_list;

position_in_list = findInList('2');  // Searches to find list index of button '2'.

So position_in_list will now contain a number between 0 and 9 because the list can hold 10 active keys.

Now you can use the list:
Code: [Select]
char keyChar;
int keyCode;
KeyState keyState;
boolean keyIsActive;

keyChar = myKeypad.key[position_in_list].kchar;            // Name of the key you are interested in.
keyCode = myKeypad.key[position_in_list].kcode;            // Position in the keymap array.
keyState = myKeypad.key[position_in_list].kstate;          // Most recent state. Pressed, Idle, etc.
keyIsActive = myKeypad.key[position_in_list].stateChanged; // true if this key just changed.

stateChanged is important so you can tell which key, or keys, in the list actually had a change.  If you are pressing 7 different keys all at the same time but you only released 2 of them, then the list will contain 2 active keys that recently changed state. 

Be sure you only read from the list.

OK that makes sense.. I couldn't figure out the Syntax on the findInList.. I sort of figured out though that based on the fact that in the loop only active Keys are present in the Key List the only real information I need for what I want to do is just the fact that the Key is IN the list or not.

Question though how does that work if you have two matrixes running is there only one list that is populated or is there a list for each matrix? If there is only one list then I would probably need to make sure that the Characters used for each list are different or I would have to figure out the code for the correct one.

mstanley

Everything in memory would be separate.  That's one of the drawbacks, it uses twice the memory.  If you run two keypads you'll get two separate lists and will have to run two separate getKeys(), one for each of the keypads.

The other thing is that you can't share pins between the keypads.  You CAN use half the pins for one keypad and the other half for the other keypad but each keypad driver would have to own its pins.

bsohn

Everything in memory would be separate.  That's one of the drawbacks, it uses twice the memory.  If you run two keypads you'll get two separate lists and will have to run two separate getKeys(), one for each of the keypads.

The other thing is that you can't share pins between the keypads.  You CAN use half the pins for one keypad and the other half for the other keypad but each keypad driver would have to own its pins.
OK thank you, That actually answered the prior question with the overlapping keypads as well but I really don't think I will need that.

Again thank you for all your help in understanding how the library works and how to use it properly.


bsohn

Hmmmm. I am getting a findInList() not declared in this scope error when compiling..

I added this to the sample my multi key sketch at the end of the loop to just find and print the position found.. but it won't compile for some reason..

Im using Arduino 1.8.5 and keypad 3.1.1

I have heard of issues with some versions of the Arduino compiler.. Wondering if I am running into that...

mstanley

Can you post your full code again?  This one that won't compile?

bsohn

#26
May 18, 2018, 07:45 pm Last Edit: May 18, 2018, 07:47 pm by bsohn
to test it I just added your code to the sample after the key press output: I did change the pins to the ones I was using from the sample pins (but that worked fine before when I was testing).

and I get 'findInList' was not declared in this scope

Code: [Select]


/* @file MultiKey.ino
|| @version 1.0
|| @author Mark Stanley
|| @contact mstanley@technologist.com
||
|| @description
|| | The latest version, 3.0, of the keypad library supports up to 10
|| | active keys all being pressed at the same time. This sketch is an
|| | example of how you can get multiple key presses from a keypad or
|| | keyboard.
|| #
*/

#include <Keypad.h>

const byte ROWS = 4; //four rows
const byte COLS = 3; //three columns
char keys[ROWS][COLS] = {
{'1','2','3'},
{'4','5','6'},
{'7','8','9'},
{'*','0','#'}
};
byte rowPins[ROWS] = {1, 2, 3, 4}; //connect to the row pinouts of the kpd
byte colPins[COLS] = {5, 6, 7}; //connect to the column pinouts of the kpd

Keypad kpd = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

unsigned long loopCount;
unsigned long startTime;
String msg;


void setup() {
    Serial.begin(9600);
    loopCount = 0;
    startTime = millis();
    msg = "";
}


void loop() {
    loopCount++;
    if ( (millis()-startTime)>5000 ) {
        Serial.print("Average loops per second = ");
        Serial.println(loopCount/5);
        startTime = millis();
        loopCount = 0;
    }

    // Fills kpd.key[ ] array with up-to 10 active keys.
    // Returns true if there are ANY active keys.
    if (kpd.getKeys())
    {
        for (int i=0; i<LIST_MAX; i++)   // Scan the whole key list.
        {
            if ( kpd.key[i].stateChanged )   // Only find keys that have changed state.
            {
                switch (kpd.key[i].kstate) {  // Report active key state : IDLE, PRESSED, HOLD, or RELEASED
                    case PRESSED:
                    msg = " PRESSED.";
                break;
                    case HOLD:
                    msg = " HOLD.";
                break;
                    case RELEASED:
                    msg = " RELEASED.";
                break;
                    case IDLE:
                    msg = " IDLE.";
                }
                Serial.print("Key ");
                Serial.print(kpd.key[i].kchar);
                Serial.println(msg);

                int position_in_list;
                position_in_list = findInList(2);  // Searches to find list index of button '2'.
                Serial.print(position_in_list);
            }
        }
    }
}  // End loop



I was wanting to test that position_in_list was getting either -1 or the number 1-9

gfvalvo

Isn't 'findInList()' a method of the 'Keypad' class, not a stand-alone function?

mstanley

Yes it is.  After all my attempts to be consistent and understandable I chose to instead be random and dismissivie in my previous explanation.  It is one of the simple evil pleasures of my life. ;)

So, I owe you an apology and will try to keep my code straight as we go through this.

bsohn

OK so given my limited knowledge of things does that mean the syntax would be...

position_in_list = kpd.findInList(2);  // Searches to find list index of button '2'.

Or am I way off...


               

Go Up