Matrix Keyboard and Arduino – Using Interrupts

Traditionally, matrix keyboards are connected to Arduino boards (and others) according to the following scheme (see https://habr.com/ru/post/460409/ )

Blue lines are columns, red lines are rows connected to the corresponding pins of the Arduino board. The sketch looks like this. (see e.g. https://3d-diy.ru/wiki/arduino-moduli/elastichnaya-matrichnaya-klaviatura-4×4/ and other internets)

const int Row[] = {11, 10, 9, 8}; // пины строк
const int Col[] = {7, 6, 5, 4};   // пины столбцов
const char k4x4 [4][4] = {      // символы на клавиатуре
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};
void setup() {
  for (int i = 0; i <= 3; i++) {  // выставляем пины строк на выход, столбцов на вход
    pinMode(Row[i], OUTPUT);
    pinMode(Col[i], INPUT_PULLUP);
    digitalWrite(Row[i], HIGH);
  }
  Serial.begin(9600);
  Serial.println("begin");

The row pins are configured as high outputs, the column pins as pull-up inputs. Further, in the Loop loop, with a frequency of 50 ms, the row pins in the loop are sequentially set to a low level, and in the nested loop, the column pins are polled. If the column has a low level (that’s how it is pulled up to a high level), then row i is closed with column j, and the k4x4(i,j) key is pressed accordingly
Everything works great, but there are a couple of things.

1. If the user presses two buttons on the same line at the same time, the high level output will be shorted to the low level output during the polling process, which may damage the board. To prevent a short circuit, protective diodes are usually installed, which somewhat complicates the circuit.

2. You may find that you need to catch a button press inside a loop running inside the loop procedure. The above method will not allow this, as the buttons will not be polled until this inner loop completes.

To eliminate the above disadvantages, the following option for connecting the keyboard and processing button presses is proposed.

The row pins are set to output low. The column pins are set to pull-high input mode. Interrupts are attached to the pins of the columns, which are triggered by a signal decay.

attachInterrupt(5, irqkb5, FALLING);

When the button is pressed, the column input is shorted to a low-level row output, the level at this input drops and an interrupt routine is called in which the column number is remembered. For example for column 5

void irqkb5 () {kbcol=5;}

After that, the procedure for determining the string is called. In fact, it is the same as in the polling option – the rows are sequentially set to a high level and it is checked whether a high level appears on our column that caused the interrupt. Thus, we will get the column and row number and, accordingly, the name of the pressed button. The only thing, to prevent shorting the outputs, the high level on the line is set not by setting the output to HIGH, but by switching to the pull-up input mode.

pinMode(rowPins[i], INPUT);digitalWrite(rowPins[i], HIGH);

After performing the procedure for determining the pressed button, the low level is restored on the lines, and interrupts are again connected to the column pins. Added 50ms post-interrupt delays and a high-level wait loop after button release to prevent chatter effects.

// 'ждем отпускания кнопки - пропускаеи дребезг
	do //-------------------
	{
		zzz=digitalread(kbcol);
		irqres=irqres+zzz;
		delay (1);
	}
	while (irqres < 20);// пока не будет 20 раз высокий -------------------

Using this method, it is possible to catch a button press (almost) at any moment of program execution, including inside any built-in loop. To do this, it is enough to insert a check of the flag (the number of the column remembered in the interrupt) inside the loop and, based on the result, decide on one or another action.

The source code for the sketch is below.

hidden text
#include <UTFT.h>
UTFT myGLCD(CTE32HR, 38, 39, 40, 41); // инициализация TFT (старого)
int TFTW = 480, TFTH = 320; // разрешение экранчика (старого)
#define clrp VGA_FUCHSIA
#define SerialUSB  Serial
#define digitalread digitalRead

int i,j,k;

extern uint8_t BigFont[];

static String btnName;
volatile static byte kbcol=0,kbrow=0, irqres=0;//'! col pin 5-8, row pin 9-13
const byte ROWS = 5; //число строк клавиатуры
const byte COLS = 4; //число столбцов клавиатуры

char* hexaKeys[ROWS][COLS] = 
{
	// названия клавиш, как на клавиатуре
	{"F1","F2","#","*"},  //13 0
	{"1","2","3","^"}, //12 1
	{"4","5","6","v"}, //11 2
	{"7","8","9","Esc"},//10 3
	{"<","0",">","Ent"}//9 4
}
;

byte rowPins[ROWS] = {9, 10, 11, 12, 13}; //к каким выводам подключаем управление строками
byte colPins[COLS] = {5, 6, 7, 8}; //к каким выводам подключаем управление столбцами

//******************
void irqkb5 () {kbcol=5;}
void irqkb6 () {kbcol=6;}
void irqkb7 () {kbcol=7;}
void irqkb8 () {kbcol=8;}

//******************
void kbdcheck (byte kbcol) 
{
	detachInterrupt(kbcol); //
	int zzz=LOW;
	btnName="---";
	irqres=0;
	delay(50); // 'ждем пока кончится дребезг
	do //-------------------
	{
		zzz=digitalread(kbcol);//' проверяем что на kbcol столбце
		// с него пришло прерывание - должен быть низкий уровень
		if (zzz == LOW) //' все норм
		{
			break; //'выходим 
		}
		irqres++;
		myGLCD.print("84: wrong "+String(kbcol)+"=" + String(irqres), 190, 80);
		if (irqres >20 ) //' 
		{
			goto kbdcheckExit1;
			//если так и не стал низким, то вообще выходим
		}
	}
	while (zzz==HIGH );// если уровень почему-то высокий, читаем еще до 20 раз 
	for (i = 0; i < 5; i++) 
	//перебираем по строкам, последовательно устанавливаем их в высокий и смотрим
	//стал ли на столбце высокий уровень
	{
		//zzz=0;
		digitalWrite(rowPins[i], HIGH); pinMode(rowPins[i], INPUT);digitalWrite(rowPins[i], HIGH);  //'устанавливаем строку c pin i в высокий
		zzz=digitalread(kbcol);
		if (zzz == HIGH) //' если он стал высоким, значит нажата кнопка kbcol,9 то есть "<-"
		{
			btnName=hexaKeys[4-i][kbcol-5]; //'! "<-"; 
			kbrow=5-i;
			break;
		}
		//else
	}
	//******
	pinMode(13, OUTPUT); digitalWrite(13, LOW);
	pinMode(12, OUTPUT); digitalWrite(12, LOW);
	pinMode(11, OUTPUT); digitalWrite(11, LOW);
	pinMode(10, OUTPUT); digitalWrite(10, LOW);
	pinMode(9, OUTPUT); digitalWrite(9, LOW);
	irqres=0;
	myGLCD.print("161:btnName=" + btnName+"  ", 20, 30);
	// 'ждем отпускания кнопки - пропускаеи дребезг
	do //-------------------
	{
		zzz=digitalread(kbcol);
		irqres=irqres+zzz;
		delay (1);
	}
	while (irqres < 20);// пока не будет 20 раз высокий -------------------
	kbdcheckExit1:
	attachInterrupt(5, irqkb5, FALLING); // 'setup()
	attachInterrupt(6, irqkb6, FALLING); //RISING
	attachInterrupt(7, irqkb7, FALLING); //RISING
	attachInterrupt(8, irqkb8, FALLING); //RISING

}
//********************************************************************************
void setup()
{
	// 'rows
	pinMode(13, OUTPUT); digitalWrite(13, LOW);
	pinMode(12, OUTPUT); digitalWrite(12, LOW);
	pinMode(11, OUTPUT); digitalWrite(11, LOW);
	pinMode(10, OUTPUT); digitalWrite(10, LOW);
	pinMode(9, OUTPUT); digitalWrite(9, LOW);
	//'cols
	pinMode(5, INPUT); digitalWrite(5, HIGH);
	pinMode(6, INPUT); digitalWrite(6, HIGH);
	pinMode(7, INPUT); digitalWrite(7, HIGH);
	pinMode(8, INPUT); digitalWrite(8, HIGH);
	
	attachInterrupt(5, irqkb5, FALLING); // 'setup()
	attachInterrupt(6, irqkb6, FALLING); //RISING
	attachInterrupt(7, irqkb7, FALLING); //RISING
	attachInterrupt(8, irqkb8, FALLING); //RISING
	
	myGLCD.InitLCD(3);    // Setup the LCD
	//SerialUSB.begin(115200);  // initialize the serial communication:
}
//***************************************************************
void loop()
{
	myGLCD.setFont(BigFont);
	myGLCD.setColor(VGA_WHITE);
	myGLCD.clrScr(); // Clear the screen and draw the frame
	// Цикл измерения и отрисовки ===============================================
	while (1)
	{
		beginizm:
		kbcol = 0;
		do //-------------------
		{
			if (kbcol>0 ) 
			{
				detachInterrupt(5); //
				detachInterrupt(6); //
				detachInterrupt(7); //
				detachInterrupt(8); //
				break; // 
			}
		}
		while (kbcol == 0);//-------------------
		kbdcheck (kbcol) ;
		myGLCD.print("252 kbcol=" + String(kbcol), 20, 150);
		myGLCD.print("252 kbrow=" + String(kbrow), 20, 170);
		myGLCD.print("255:btnName=" + btnName+"   ", 20, 30);
		// 'delay (10);
	}
	//' end while (1)
}

After interrupt processing (learning the column number), interrupts are disabled,

detachInterrupt(5); //

and after the completion of the procedure for determining the pressed button, they turn back on.
To break the built-in loop at its beginning, you can insert, for example, such a condition

			if (kbcol>0 ) 
			{
				break; // 
			}

Thus, pressing any button will exit the loop.

Similar Posts

Leave a Reply