8 years, 2 months ago.

Having trouble implementing a scan matrix with leds and buttons.

Hi,

I'm working on a scan matrix of 8 leds and 8 buttons, they are configured as in the picture (minus the diodes, need to go get some but it shouldn't be causing the problems I'm having) /media/uploads/rtrann/tr808-schem-snip.png .

Currently I'm testing with just 4 leds and two buttons to try and get the basics right. The idea is to have a button toggle a specific led on and then off when pressed again. This is going to be imported into a larger project so I need this to be relatively fast.

I've tried a couple different methods and cant quite seem to get it functioning right. Let me preface the next bit by saying that I have tested all the connections and I can light up any led, or toggle any button without error. So the connections are good.

My first attempt was a loop at a specific interval that goes through and asserts a column, checks for a button press and then sets a flag to keep the led on. It then de asserts the column and moves to the next. If during the loop the flag is true and the correct button press is found, it will turn off that led and reset the flag. The problem I found with this approach is that scanning fast enough to get the leds to appear solid resulted in multiple scans of the same button during a single press. This results in flip flopping of values due to making two of my if statements alternate between true. Giving me some % chance to actually get what I want out of the check. The code works about half the time give or take, but I'm not sure how to get around this flip flopping of values. My approach may just be the wrong one.

scan_matrix

#include "mbed.h"
#include "DebounceIn.h"

DigitalOut r1 (p25); //row1
DigitalOut r2 (p24);  //row2
DebounceIn r3 (p26);  //row3
DigitalOut c1 (p22);  //column1
DigitalOut c2 (p23);  //column2

//Ticker         Led_Matrix;
//Ticker         Button_Matrix;

//led flags
int r1c1 = 0;
int r1c2 = 0;
int r2c1 = 0;
int r2c2 = 0;

void assign_matrix(int val1, int val2, int val3, int val4) {
    r1 = val1;
    r2 = val2;
    c1 = val3;
    c2 = val4;
}

void init_matrix() { //clear matrix
    assign_matrix(0,0,0,0);
}

void led1() {
    assign_matrix(1,0,0,1);
}

void led2() {
    assign_matrix(1,0,1,0);
}

void led3() {
    assign_matrix(0,1,0,1);
}

void led4() {
    assign_matrix(0,1,1,0);
}

void b1() {
    assign_matrix(0,0,1,0);
}

void b2() {
    assign_matrix(0,0,0,1);
}

void scan(void) {
    b1();                               //set matrix for reading of button 1
    wait(0.01);                         //appears to need min 10ms to set the column, otherwise glitchey behavious starts
    
    if (r3 && !r1c1) {
        led1();                         //light led1
        wait(0.001);
        r1c1 = 1;                       //led1 flag
        printf ("r1c1_1 = %d\n", r1c1); //test point..
    } 
    if (r1c1) {
        led1();
        wait(0.001);
        r1c1 = 1;
        printf ("r1c1_2 = %d\n", r1c1);//test point..
    }
    if (r3 && r1c1) {
        init_matrix();
        wait(0.001);
        r1c1 = 0;
        printf ("r1c1_3 = %d\n", r1c1); //test point..
    }    
    //init_matrix();
    //wait(0.01);
    
    b2();                               //set matrix for reading of button 2
    wait(0.01); 
    if (r3 && !r1c2) {                  //button press + toggle on led
        led2();                         //light led2
        wait(0.001);
        r1c2 = 1;                       //led2 flag
        printf ("r1c2_1 = %d\n", r1c2);//test point..
    }
    if (r1c2) {                         //light led if flag is toggled
        led2();
        wait(0.001);
        printf ("r1c2_2 = %d\n", r1c2); //test point..
    }
    if (r3 && r1c2) {                   //button press + toggle off led
        init_matrix();                  //turn off leds
        wait(0.001);
        r1c2 = 0;
        printf ("r1c2_3 = %d\n", r1c2); //test point..
    } 
}

int main() {
    init_matrix();       //init matrix state
    printf("\n\n INIT MATRIX START\n\n");
    while (1) {
        scan();
        wait(0.001);
    }
}

Here is a sample of the terminal output after the initial button press, so you can see what is happening with the values.

  • r1c1_2 = 1
  • r1c1_2 = 1
  • r1c1_2 = 1
  • r1c1_2 = 1
  • r1c1_2 = 1
  • r1c1_2 = 1
  • r1c1_2 = 1
  • r1c1_3 = 0
  • r1c1_1 = 1
  • r1c1_2 = 1
  • r1c1_2 = 1
  • r1c1_3 = 0
  • r1c1_2 means the led is on and no button is pressed
  • r1c1_3 means a button press is identified and the led is turned off
  • r1c1_1 means a button press is identified and the led is turned on.

That was a relatively good one however, it can flip flop between r1c1_3 and r1c1_1 a lot. That means I have to keep pressing the button until I get a value of r1c1_3 = 0 to turn off the led.

Alternatively I've considered using two scans, a slower one for the buttons (100ms) and a faster one for the leds (1ms). But I tested this out with two tickers and it didn't work right. I'm not sure how two tickers of differing speeds interact with each other. I found that if I used the 1ms ticker the 100ms ticker never seemed to be able to execute any of its code.

Anyways, looking for advice or solutions. I know matrix scanning is fairly common so I'm hoping people more experienced with this will just say "oh you're not doing this right, this is the way".

Thanks, Ryan

Hey R T.

I tryed to write some code here, but I am a little busy so I couldn't finish. What I was trying to do is to save both last and newer status of the button so you can compare them in order to decide if you have to turn the led on or not. It would solve your problem with the time that the button is pressed. Let me give an example:

If both new and old status are 0, if means that the button is not and was not pressed

if new =1 and old = 0, then it is pressed and you change the led status

if both 1 someone is still pressing that button

if new =0 and old = 1 the the button was released;

thiago

posted by Thiago . 23 Feb 2016

Thanks Thiago, I think you're on the right track for sure. I"ll try and implement this.

posted by R T 23 Feb 2016

1 Answer

8 years, 2 months ago.

Personally I'd use BusIn/BusOut rather than individual digital ins and outs. It allows you to use a for loop to move between inputs/outputs. With your current structure the code would become unmanageable if your matrix got to be more than a few pins on each side. If nothing else the risk of a bug caused by a copy and paste with a change missed becomes huge.

The code below should toggle the LED that corresponds to a button each time that button changes state.

#include "mbed.h"

// Row / Col pin setup.
// defines must match number of pins in the bus structures.

#define numberOfCols 2
BusOut cols(p22,p23);

#define numberOfInRows 1
BusIn inRows(p26);

#define numberOfOutRows 2
BusOut outRows(p25,p24);

// Holds the last state of each input pin in a 2d array.
// Not very memory efficent but simple.
int lastState[numberOfInRows][numberOfCols]; 

// Sets the output states. Each col is stored as a seperate value.
// Within each col each bit indicates the state of an output row pin.
// e.g. with outRows(p25,p24) and outputState[this col] = 0x02
// p25 would be high and p24 low.
int outputState[numberOfCols]; // last state of each row/column

// Sets the outputs and checks the inputs for a given col.
// returns a binary mask of which inputs have changed.
// 0 = nothing has changed. 0x01 = least significant row has changed, 0x03 = two least significant rows changed etc...
int scanCol(int colToCheck)
{
    int changedInputs = 0;
    outRows = outputState[colToCheck];
    cols = 1<<colToCheck;
    wait(0.01);
    for (int i=0; i<numberOfInRows; i++) {
        if (inRows[i] != lastState[i][colToCheck]) {
            // row i, col colToCheck has changed
            changedInputs &= 1<<i;
            lastState[i][colToCheck] = inRows[i];
        }
    }
    cols = 0;
    outRows = 0;
    return changedInputs;
}

main ()
{
    int currentCol = 0;
    while (true) {
        // get a list of changed button states
        int changedPins = scanCol(currentCol);
        
        // toggle the LED related to the changed button. (use a bitwise exclusive or)
        outputState[currentCol] ^= changedPins; 
        
        //  for more complex button press logic use this...
        for (int i=0; i<numberOfInRows; i++)
          if (changedPins & (1<<i)) {
            // input row i, col currentCol has changed
            // lastState[i][currentCol] gives current state of that pin.
          }
        
        
        currentCol++;
        if (currentCol == numberOfCols)
            currentCol  = 0;
    }
}

Accepted Answer

Thanks for this I'm going to try it out now.

posted by R T 23 Feb 2016

Hi Andy,

I've been messing around with your program and I have questions/comments.

As is the program isn't toggling leds so I attempted to fix it.

On line 37 you have this, but it doesn't seem right to me.

changedInputs &= 1<<i;

I'm still thinking of what this should be but I have changed it to

changedInputs = 1<<i

That enabled the leds to light up but they just flash instead of staying lit till the next button press. This is due to the last state variable being activated twice per press (On then OFF). So the last state is always a 0. Also at this point the incorrect LED is being triggered. It's the proper row but the wrong column (col 1 instead of 0 when there is a button press on col 0)

Line 12 is to set the active column, with the shift it is moving from right most column to the left most. It seems that the buttons are the opposite? Or maybe its the saved button state information that is filpped? For now I've swapped the button connections (even though I triple checked and they were already correct...) and that works.

cols = 1<<colToCheck;

I've got the toggling working on the leds and with the hardware connection change the correct led is lighting. Here is the edited code.

Edited code

#include "mbed.h"
 
// Row / Col pin setup.
// defines must match number of pins in the bus structures.
 
#define numberOfCols 2
BusOut cols(p22,p23);
 
#define numberOfInRows 1
BusIn inRows(p26);
 
#define numberOfOutRows 2
BusOut outRows(p25,p24);

// Holds the last state of each input pin in a 2d array.
// Not very memory efficent but simple.
int lastState[numberOfInRows][numberOfCols]; 
 
// Sets the output states. Each col is stored as a seperate value.
// Within each col each bit indicates the state of an output row pin.
// e.g. with outRows(p25,p24) and outputState[this col] = 0x02
// p25 would be high and p24 low.
int outputState[numberOfCols]; // last state of each row/column
 
// Sets the outputs and checks the inputs for a given col.
// returns a binary mask of which inputs have changed.
// 0 = nothing has changed. 0x01 = least significant row has changed, 0x03 = two least significant rows changed etc...
int scanCol(int colToCheck)
{
    int changedInputs = 0; //init value
    outRows = outputState[colToCheck];
    cols = 1<<colToCheck; //sets active column
    wait(0.01);
    for (int i=0; i<numberOfInRows; i++) {
        if (inRows[i] != lastState[i][colToCheck]) { //does the current state of the selected in row equal the last state
            // row i, col colToCheck has changed
            //changedInputs = 1<<i;   //changed &= to =
            if (inRows[i] == 1) {
                changedInputs = 1<<i;
            }
            lastState[i][colToCheck] = inRows[i];
        }
    }
    cols = 0; //turn off Ouptuts
    outRows = 0; //turn off Outputs
    return changedInputs;
}
 
main ()
{
    printf("Matrix Start\n\n");
    int currentCol = 0;
    while (true) {
        //wait(1);
        int changedPins = scanCol(currentCol); // get a list of changed button states for the current column
        outputState[currentCol] ^= changedPins; // toggle the LED related to the changed button. (use a bitwise exclusive or)
        //  for more complex button press logic use this...
        /*
        for (int i=0; i<numberOfInRows; i++)
          if (changedPins & (1<<i)) {
            // input row i, col currentCol has changed
            // lastState[i][currentCol] gives current state of that pin.
          }
        */
        currentCol++;
        if (currentCol == numberOfCols) {
            currentCol  = 0;
        }
    }
}

Tomorrow I will try and hook up the full 8x8 matrix and see if this will scale up properly. Let me know if you see any issues with the edited code, or the above.

Thanks, Ryan

posted by R T 23 Feb 2016

Line 37 - you're correct there is an error there, I'm not sure what I was thinking with an & there. But it should be

changedInputs |= 1<<i;

We want to do a binary OR so that a flag gets set for each button that's down. Your fix will only return the highest row that has had a button change rather than all of them. True it's unlikely that two buttons will ever change at the exact same time but we should still cope with that situation correctly.

You've changed scanCol to only flag up buttons that have been pressed this scan rather than buttons that have changed in either direction. That certainly gives the behavior you want right now but what if at some point in the future you want to detect when a button is released or measure how long a button is held for? Personally I'd keep the scanning code so that it flags any button change and then put any required the direction sensitivity in the code that calls it.

//       outputState[currentCol] ^= changedPins; // toggle the LED related to the changed button. (use a bitwise exclusive or)
        //  for more complex button press logic use this...

        for (int i=0; i<numberOfInRows; i++) {
          if (changedPins & (1<<i)) {             // input row i, col currentCol has changed
            if (lastState[i][currentCol]) // button is now down
               outputState[currentCol] |= 1<<i // set the i'th bit
            else // button is now up
               outputState[currentCol] &= ~(1<<i) // clear the i'th bit 
          }       
       }

Also I just realized the LEDs are probably doing the opposite of what they should. The column line gets set high when we scan a column. Which means we should set the LED row lines low to turn an LED on not the high that is currently used. Rather than deal with negative logic in the code it's probably more intuitive to keep it so that the outputState variable uses a 1 to indicate on and 0 to indicate off and then invert it when we output the values to the pins.

int scanCol(int colToCheck)
{
    int changedInputs = 0; //init value
    outRows = ~outputState[colToCheck]; //  LED outputs low for on, outputState is 1 for on so use the bitwise inverse 
   ... row scan logic...
    cols = 0; //turn off column
    outRows = 0xffff; //turn off all LED outputs
    return changedInputs;
}

And one final tidy up. We really should set an initial value for outputState rather than just hoping that it starts with all 0's.

for (int i = 0;i<numberOfCols;i++)
  outputState[i] = 0;

somewhere near the start of main().

posted by Andy A 24 Feb 2016

Ok I will change that line to |=, that makes sense.

In regards to the button presses, I forgot to mention that these buttons are momentary so they will never be held high unless someone is physically doing that. The program I will be writing only need buttons to trigger when they are 1's so this is fine as I have it. Good to know for the future though.

As for the LEDs, as in the schematic they will only trigger if the row is set High and the column is set low so that bit of code you posted will work in the opposite manner (all on, then a button press turns an LED off).

I found that if I change

outRows = outputState[colToCheck];

to

outRows = outputState[1>>colToCheck];

Then the Leds line up with the button positions correctly. However this breaks again when I add the 3rd and final column.

Right now I have the matrix set up as 4x3 (2 rows of leds, 2 rows of buttons and 3 columns). Adding another row was simple and caused no issue however when I added a 3rd column the led triggering has become strange. A button press currently lights the Next 2 LED's (not the one above the button), they are on the correct row though. For example if the matrix is like this:

  • A) L, L, L
  • B) L, L, L
  • C) B, B, B
  • D) B, B, B
  • _ _1_2_3
  • C1 triggers leds A2 + A3 (should be A1),
  • C2 triggers leds A3 + A1 (should be A2)
  • C3 triggers leds A1 + A2 (should be A3).
  • The same goes for button presses on row D except leds trigger on row B.

I'm investigating this at the moment, it would seem that there is a bit shift happening and perhaps some bitwise operation to trigger all the leds except for the one I want. Or maybe columns are being stuck high when they should be reset. But I don't get it at the moment because it looks like the leds are triggered on a per column basis so this shouldn't be happening.

And yea I added the init of output state in the beginning of main.

Thanks, Ryan

posted by R T 25 Feb 2016

For the diagram in your initial post the column must be high and the row low for an LED to turn on, you are saying yours are the other way around.

If your LEDs are the other way around and they turn on when the column is low and the row high then the best thing to do is invert the column output so that the column lines are idle high and active low. This also means that the buttons will be low for active and high for idle.

int scanCol(int colToCheck)
{
    int changedInputs = 0;
    outRows = outputState[colToCheck]; // no longer invert this, we want a 1 for LED on in the row.
    cols = ~(1<<colToCheck);  // all high other than the active bit
    wait(0.01);
    for (int i=0; i<numberOfInRows; i++) {
      if (inRows[i] != lastState[i][colToCheck]) {
        if (inRows[i] == 0) {  // low == pressed
            changedInputs != 1<<i;
        }
        lastState[i][colToCheck] = inRows[i];  // note - lastState will also contain 0 for pressed and 1 for not pressed.
      }
    }
    cols = 0xffff; // all columns to 1
    outRows = 0; // all rows to 0
    return changedInputs;
}

Also it would be a good idea to add

inRows.mode(PullUp);

at the start of main so that the button inputs are pulled high (the new idle state) when not pressed.

posted by Andy A 25 Feb 2016

Thanks andy that fixed it all up. I actually didn't notice that the leds in the example schematic I posted were active low on the rows, sorry about that. I was using that schematic and another one to build my matrix, the second schematic suggested I make use of active High on the rows since there are pull ups in the micro controller.

Anyways, you've been a big help. Take it easy.

Ryan

posted by R T 25 Feb 2016

You're welcome.

posted by Andy A 25 Feb 2016