Scanning pins speedup

20 May 2010 . Edited: 20 May 2010

Hi all, I am working on implementing a scanning button matrix (see here).  It works well, but the switching is slow enough to be in the audio range (about 11khz).  This is an audio application so this just won't do.  I'm working on speeding up the code but am not sure what to do.  Basically, there are 64 "buttons" - 8 rows, 8 columns.  The rows are inputs, the columns are outputs.  Ideally, the outputs all normally sit in tri-state (disconnected) mode, but this is only partially possible on the mbed.  The microcontroller sets one of the outputs as low, then runs through the column pins and looks for a low signal.  If one is found, that means that that button is down.  Then, it turns the column back to "tri-state" and checks the next column (this is explained better in the link above).  In order to simulate tri-state on the mbed, I'm making the column pins DigitalInOuts.  That way, "tri-state" is setting the pin as an input with mode(PullNone).  Unfortunately, this means I have to toggle the pin mode at every iteration, which is much slower than just writing a value.  Here is the code:

 

    // Set up input pins
    DigitalIn inStrings[8] = {p5, p6, p7, p8, p9, p10, p11, p12};
    // Set up cyclic output pins
    DigitalInOut outStrings[8] = {p13, p14, p15, p16, p17, p18, p19, p20};
    // Indexing variables
    int i;
    int j;

    // Setup pins
    for (i = 0; i < numStrings; i++) {
        // Pull-up resistors on input pins
        inStrings[i].mode(PullUp);
        // Set all out strings as input to being with
        outStrings[i].input();
        outStrings[i].mode(PullNone);
    }
    
    // For reading in values
    char reading = 0;
    while (true) {
        // Check buttons
        for (i = 0; i < numStrings; i++) {
            // Make the i'th row output 0V
            // The opposite of what the inputs are sitting at
            outStrings[i].output();
            outStrings[i].write(0);
            for ( j = 0; j < numStrings; j++ ) {
                // Read in value from j'th column
                reading = inStrings[j].read();
                
                if ( reading ) {
                    // Handle a button press (code omitted for clarity)
                }
                
                if ( !reading ) {
                    // Handle a button up (code omitted for clarity)
                }
            }
            // OK, now we are done with this row, do it again.
            // Make this row an input so that it doesn't affect
            // future readings
            outStrings[i].input();
            outStrings[i].mode(PullNone);
        }
    } 

I'd like to use BusInOut or PortInOut but I need to be able to set individual pins as inputs or outputs so this doesn't seem possible.  Any tips?  Is there any way I can make the outString default to PullUp when it is set to an input?  Omitting that instruction makes the switching rate much faster but the code stops working.  Thanks!

-Colin

20 May 2010

Hi Colin,

Yes, it should certainly be possible to improve this. Here is a few notes:

  • For your inStrings, you could use a BusIn or PortIn - just read the whole input bus in one go. Although the way you have it is nice and symmetrical, so may not be an improvement.
  • Check you are on the latest version of the library; we just sped up some things incase you haven't pulled that in
  • My intention was that both your .mode(PullNone) and .write(0) could be lifted outside the loop, setup only once; i.e. you'd just go through setting .output() then .input() for each iteration. If you are saying this is not working, we can have a look at that and see if that can be fixed.

Can you confirm that if you only setup .mode() and .write(0) at the start for each InOut, then the code isn't working. If so, we'll take a look.

Simon

20 May 2010

If you are on the latest version of the library, you can also set .mode(PullNone) and .mode(OpenDrain) (two successive instructions) for each of your outStrings-Pins (configured as outputs). Then in your loop you can just set the outStrings-Pins to 0 and 1 (.write(0) and .write(1)) and so on.

When the OpenDrain-Mode for a Port-Pin is selected, it is actually only driven LOW actively as an output when set to 0. When set to 1, the Pin is automatically and temporarly (while being set to 1) reconfigured as input by hardware with the current input-mode (PullUp, PullDown or PullNone).

Best regards
Neni

20 May 2010

Simon Ford wrote:

Hi Colin,

Yes, it should certainly be possible to improve this. Here is a few notes:

  • For your inStrings, you could use a BusIn or PortIn - just read the whole input bus in one go. Although the way you have it is nice and symmetrical, so may not be an improvement.
  • Check you are on the latest version of the library; we just sped up some things incase you haven't pulled that in
  • My intention was that both your .mode(PullNone) and .write(0) could be lifted outside the loop, setup only once; i.e. you'd just go through setting .output() then .input() for each iteration. If you are saying this is not working, we can have a look at that and see if that can be fixed.

Can you confirm that if you only setup .mode() and .write(0) at the start for each InOut, then the code isn't working. If so, we'll take a look.

Simon

Hi Simon - thanks for the tips, I will tweak my inStrings.  I am using the latest library (did this yesterday).  I think that your last tip is as I'm doing it, except omitting the .mode(PullNone) at the end - and indeed, this does not work:

    while (true) {
        // Check buttons
        for (i = 0; i < numStrings; i++) {
            // Make the i'th row output 0V
            // The opposite of what the inputs are sitting at
            outStrings[i].output();
            outStrings[i].write(0);
            for ( j = 0; j < numStrings; j++ ) {
                // Read in value from j'th column
                reading = inStrings[j].read();
                
                if ( reading ) {
                    // Handle a button press (code omitted for clarity)
                }
                
                if ( !reading ) {
                    // Handle a button up (code omitted for clarity)
                }
            }
            // OK, now we are done with this row, do it again.
            // Make this row an input so that it doesn't affect
            // future readings
            outStrings[i].input();
            //outStrings[i].mode(PullNone);
        }
    } 
Thanks again,

-Colin

20 May 2010

Nenad Milosevic wrote:

If you are on the latest version of the library, you can also set .mode(PullNone) and .mode(OpenDrain) (two successive instructions) for each of your outStrings-Pins (configured as outputs). Then in your loop you can just set the outStrings-Pins to 0 and 1 (.write(0) and .write(1)) and so on.

When the OpenDrain-Mode for a Port-Pin is selected, it is actually only driven LOW actively as an output when set to 0. When set to 1, the Pin is automatically and temporarly (while being set to 1) reconfigured as input by hardware with the current input-mode (PullUp, PullDown or PullNone).

Best regards
Neni

Ah, this is smart, I will try this too.  Thanks.

-Colin

20 May 2010 . Edited: 20 May 2010

Ack!  I just realized I had not hit the update button.  Now things are faster.  I will still try the speed-ups... will report back later.

-Colin

20 May 2010

So the OpenDrain tip works, but I'm actually getting a problem where multiple adjacent presses are being triggered - what I'm guessing is happening is that when you set the pin to 1 on OpenDrain it is actually a sort of slow transition, slow enough that multiple presses are happening.  I think I will try to use PortOut and read in all port values, then set it to 1, then do all the readings - this should speed things up more, but also give the open drain more time to react.

-Colin

21 May 2010

The reason for the detection of multiple presses could also be, that the whole scanning process is now very fast. Maybe you have to implement some code to debounce the buttons.

Neni

21 May 2010

Hi Neni, I do have some debouncing code.  It was omitted for clarity.  It's based on a counting method, and I can try increasing the count requirement.

-Colin

21 May 2010

The behaviour of the mbed library as it currently stands is that it does not preserve the mode when changing between input and output. We'll have a look in the next batch of changes to see if it is a good idea to change this behaviour. Also, we have some plans to expand the functionality of the Port functions, as you have highlighted here. Hopefully these will be implemented and released soonish.

21 May 2010

Hi Jon,

but when the OpenDrain-mode is used and the outout/input-change is done by hardware automatically, then the mode-settings are preserved (since there's no intentional output/input-change by library-functions), or am i wrong?

Regards
Neni

21 May 2010

That is correct - the Open Drainness (if that is a word) is separate to the Pullness of the pin. However, if call input() or output() on a pin that has been defined as Open Drain, then you will lose that setting.

23 Dec 2010

The BusOut pins must have diodes ... in order to prevent forcing outputs of mbed ( in case of 2 buttons of the matrix pressed once - accidentaly or not - of the same column two output rows may became comencted each other even if they have different output states). The catodes of the diodes must be to mbed - this way only the "0" state will be seen. "1" or TS will be default for the released button.

You gain speed also by not having to change "tri-state" or else.

Values must be read also with BusIn (for speed) - as posted above. For speed also BusOut could be a rotating "0" value of a byte. A rotate instruction is much faster then seting individual bits or loading a value for the byte.

If not all the time a key is pressed . you could set DigitalIn pins of BusIn as IntrerruptIn, your program runs until a interrupt is sensed on any of the inputs and then it calls a service routine to scan the keymatrix.

You said that the speed is not enough for audio .. I don't understand this.. you cannot press the keys faster then 100ms let's say - nothing to do with the audio range (debouncing and hands speed :) so what's the hurry with that 11khz ? In the future be more specific with app is you can if you really need good help.