Matrix of toggle switches and tactile buttons

Hi,
I am working on a project in which i need to read the state about 80 momentary and toggle switches.
I decided to organize them in a matrix.
So I designed a pcb where there are 80 molex 1x2 molex connectors in 8 cols and 10 rows to connect the switches. Each has a diode and connected, to prevent masking and ghosting.
And will also contain a 10x1 and 8x1 connector to connect the rows and cols to the Arduino.
For now I created the prototype on a perfboard just as a 2*8 matrix.
I need to check the switch states every loop and if the state of the switch has changed i need to send a message to the PC on serial. So I wrote the code that uses the internal pullup resistors of the arduino.
It works almost well, but sometimes I get the message twice, which for me means that the switches are bouncing...
The problem is that i have no idea how to correctly debounce a matrix.
And also have no idea if it is a good idea to put momentary and toggle switches in the same matrix.
I think the debounce should be done by checking the switch states out a bit later and if the state is still the same, than it is not because of bouncing, but i cannot use delay() because other inputs and outputs should not be blocked. (I need to add several leds and servos later to the project...)

My code is:

//Initialise the matrix for the switches and push buttons
const byte SWITCH_PANEL_ROWS = 2;
const byte SWITCH_PANEL_COLS = 8;

byte SWITCH_PANEL_ROW_PINS[SWITCH_PANEL_ROWS] = {2, 3}; 
byte SWITCH_PANEL_COL_PINS[SWITCH_PANEL_COLS] = {14,15,16,17,18,19,20,21}; 

//Create an array to store the states of the switches
bool switchPanelLastState[SWITCH_PANEL_ROWS][SWITCH_PANEL_COLS];

//Settings for serial
const int BAUD_RATE = 9600;
const int SERIAL_TIMEOUT = 50;

//Is pc connected?
bool isComputerConnected = false;

//Declare functions
void ScanSwitchPanel(bool initialScan = false);
//End of declare

void setup() 
{
  //First set the row pins
  for(byte i=0; i < SWITCH_PANEL_ROWS; i++)
  {
    pinMode(SWITCH_PANEL_ROW_PINS[i], INPUT_PULLUP);
  }

  ScanSwitchPanel(true);
  
  //Start serial for testing
  Serial.begin(BAUD_RATE);
  Serial.setTimeout(SERIAL_TIMEOUT);
}

void loop() {
  //Read message from PC if available
  if(Serial.available())
  {
    char commandBuffer = Serial.read();
    int valueBuffer = Serial.parseInt();
	//If we get C1 it means that the SimConnect client app
	//on the PC wants to connect the Arduino
	//C0 means, that the softaware on PC has quited
    if(commandBuffer == 'C')
    {
		if(valueBuffer == 1) 
		{
			isComputerConnected = true;
			Serial.print('C');
			Serial.print(1);
			Serial.print('\n');
			//Then we need to send the initial states of the switches
			for (byte r = 0; r < SWITCH_PANEL_ROWS; r++)
			{
				for (byte c = 0; c < SWITCH_PANEL_COLS; c++)
				{
					Serial.print('s');
					Serial.print(r);
					Serial.print(',');
					Serial.print(c);
					Serial.print(',');
					Serial.print(switchPanelLastState[r][c] ? 1 : 0);
					Serial.print(';');
				}
			}
			//Then indicate that we finished the transmission
			Serial.print('I');
			Serial.print(';');
		}
		else if (valueBuffer == 0)
		{
			isComputerConnected = false;
		}
    }
  }
  ScanSwitchPanel();
}

void ScanSwitchPanel(bool initialScan )
{
	for (byte c = 0; c < SWITCH_PANEL_COLS; c++)
	{
		//We set the column value LOW
		pinMode(SWITCH_PANEL_COL_PINS[c], OUTPUT);
		digitalWrite(SWITCH_PANEL_COL_PINS[c], LOW);

		//Read the row values
		for (byte r = 0; r < SWITCH_PANEL_ROWS; r++)
		{
			if (initialScan)
			{
				//If it is the first scan of the switches we save all values
				switchPanelLastState[r][c] = //We need to negate becuase we read 
					!((bool)digitalRead(SWITCH_PANEL_ROW_PINS[r])); //HIGH if switch is off and LOW when it is on :)
			}
			else
			{
				//Otherwise we only save if it has changed
				if (switchPanelLastState[r][c] != !((bool)digitalRead(SWITCH_PANEL_ROW_PINS[r])))
				{
					//We save the value 
					switchPanelLastState[r][c] = !((bool)digitalRead(SWITCH_PANEL_ROW_PINS[r]));
					//And then send the serial message if PC is connected
					if (isComputerConnected)
					{
						Serial.print('S');
						Serial.print(r);
						Serial.print(',');
						Serial.print(c);
						Serial.print(';');
						//Serial.flush();
					}
				}
			}
		}

		//Set it back to HIGH
		digitalWrite(SWITCH_PANEL_COL_PINS[c], HIGH);
		pinMode(SWITCH_PANEL_COL_PINS[c], INPUT);

	}

}

And I am enclosing the schematics of the board.

What are your suggestions for debouncing my matrix?

Thanks in advance for any help!

Hi,
Try a library called keypad.

http://playground.arduino.cc/Code/Keypad

It uses a matrix arrangement like you have.

Tom... :slight_smile:

Hi,
Thanks for the quick answer.
Is it possible to use this library without using any keyMap?
As far as i can see in the example it is unavoidable.
I need to send the position of the switch that has changed in the matrix, so creating a keymap would't be too effective for me I think. Or am I misunderstanding this library?

Thanks in advance.

Hi,
I studied a bit the source of Keypad.h.
I saw many things that were unnecessary for me, and also, that I didn't understand.
So i decided to write an own one corresponding to my needs.
I add here the header and the cpp file, in case anyone may need it later.
It might not be the best soultion, but works quite well for me.
At the moment I don't have momentary buttons with me to test, but with toggle switches it works well.

So here is the SwitchPanel.h

/*
* SwitchPanel.h
*
* Created by Agoston Diamont
* Date: January 05 2017
*
* This Class represents the switch panel
* for the C172 home cockpit
*
*/
#ifndef _SWITCHPANEL_h
#define _SWITCHPANEL_h

#if defined(ARDUINO) && ARDUINO >= 100
	#include "arduino.h"
#else
	#include "WProgram.h"
#endif

#include "CommHandler.h"

//The possible states of a switch
typedef enum { FALSE, TRUE, DEBOUNCE, NONSET } SwitchState;
//And we need to overload the == operator to be able to compare it to a logic variable
inline bool operator== (SwitchState lhs, bool rhs)
{
	if (lhs == FALSE && !rhs)
		return true;
	if (lhs == TRUE && rhs)
		return true;

	return false;
}
//And overload also the !=
//We are not supposed to use the DEBOUNCE or NONSET in neither overload
inline bool operator!= (SwitchState lhs, bool rhs)
{
	if (lhs == FALSE && !rhs)
		return false;
	if (lhs == TRUE && rhs)
		return false;

	return true;
}
//This struct represents a switch
typedef struct
{
	SwitchState currentState;
	unsigned long debounceBeginTime;
	bool debounceState;
} Switch;

//This is the class for the switchpanel
class SwitchPanel
{

public:
	SwitchPanel(CommHandler& serial);
	void SendAllSwitchStates();
	void Scan();
private:
	//Setup the size of the switch panel
	static const byte numberOfCols = 8;
	static const byte numberOfRows = 2;
	//Setup the pins 
	const byte rowPins[numberOfRows] = {2, 3};
	const byte colPins[numberOfCols] = {14,15,16,17,18,19,20,21};
	//The debounce time in milliseconds
	static const byte debounceTime = 10;
	
	//Create the matrix for the switches
	Switch switches[numberOfRows][numberOfCols];

	//The object for serial
	CommHandler& commhandler;
};



#endif

And here is the SwitchPanel.cpp

/*
* SwitchPanel.cpp
*
* Created by Agoston Diamont
* Date: January 05 2017
*
* This Class represents the switch panel
* for the C172 home cockpit
*
*/
#include "SwitchPanel.h"

SwitchPanel::SwitchPanel(CommHandler& serial)
	: commhandler(serial)
{
	//Initialize the pins and the switch structs
	for (byte r = 0; r < numberOfRows; r++)
	{
		//Activate the internal pullup resistors
		pinMode(rowPins[r], INPUT_PULLUP);

		for (byte c = 0; c < numberOfCols; c++)
		{
			//Set the cols as high impedance input
			pinMode(colPins[c], OUTPUT);
			digitalWrite(colPins[c], HIGH);
			pinMode(colPins[c], INPUT);

			//Set the state of the switch NONSET
			switches[r][c].currentState = NONSET;
		}
	}


}
//Scanning the switches
void SwitchPanel::Scan()
{
	

	for (byte c = 0; c < numberOfCols; c++)
	{
		pinMode(colPins[c], OUTPUT);
		digitalWrite(colPins[c], LOW);

		for (byte r = 0; r < numberOfRows; r++)
		{
			//We save the current read value of the switch
			//We invert because we read LOW if the switch is active
			bool switchState = (bool)!(digitalRead(rowPins[r]));
			//If the switches haven't been given the initial value yet, we give them
			//Here we don't need to debounce yet
			if (switches[r][c].currentState == NONSET)
			{
				switches[r][c].currentState = (SwitchState)switchState;
			}
			//If the switch is being debounced
			else if(switches[r][c].currentState == DEBOUNCE)
			{
				//We check if the debounce time is over or not
				if (millis() >= (switches[r][c].debounceBeginTime + debounceTime))
				{
					//Then we check if the debounce state is the same as current or not
					if (switches[r][c].debounceState == switchState)
					{
						//If yes we save that state
						switches[r][c].currentState = (SwitchState)switches[r][c].debounceState;
						commhandler.SendSwitchStateChange(r, c);
					}
					else
					{
						//If not, we reset the original state which is the opposite of the
						//state we are debouncing
						switches[r][c].currentState = (SwitchState) !switches[r][c].debounceState;
					}
				}
			}
			//If it is TRUE OR FALSE
			else
			{
				if (switches[r][c].currentState != switchState)
				{
					switches[r][c].currentState = DEBOUNCE;
					switches[r][c].debounceBeginTime = millis();
					switches[r][c].debounceState = switchState;
				}
			}

		}

		digitalWrite(colPins[c], HIGH);
		pinMode(colPins[c], INPUT);


	}
	

}
//Read the states of each switch and ask the communication handler to send it to PC
void SwitchPanel::SendAllSwitchStates()
{
	for (int r = 0; r < numberOfRows; r++)
	{
		for (int c = 0; c < numberOfCols; c++)
		{
			commhandler.SendSwitchInitialState(r, c, switches[r][c].currentState);
		}
	}
	//And the signal that we finished sending
	commhandler.SendSwitchInitComplete();
}

A bit setup needed in the .h. I wanted to get the size of the panel and the pins from the constructor, but I couldn't find a nice solution for that...

And I am using the CommHandler class, that was created by me also to handle my serial communication with PC, so in case someone wants to use this, need to modify a bit.
But I hope it will be helpful for someone in the future.

And also i would be happy to hear any good advices to improve this further.

:slight_smile:

Hi,
Good that you solved your problem, have you go a brief document and/or example to show hoe to use your library?

I know what you mean by a library having commands you don't need, and if you can write one that does the bare necessities then thats great.

Tom... :slight_smile:

Hi,

I did some modifications to make it a bit more universal(I took all commands out that is based on my communication handler library, and added a possibility to add an event handler), created an example file, and a github folder for this small library.
Here is the url:

I would be happy if you could check it out, this is my first library published,
so I hope everything is as it should be.
I hope it will be useful :slight_smile:

Have a great day,
Agoston

1 Like