Pull to refresh

Матричная клавиатура и Ардуино — использование прерываний

Reading time6 min
Views7.3K

Традиционно матричные клавиатуры подключают к платам Ардуино ( и другим) по следующей схеме (см. https://habr.com/ru/post/460409/ )

Синие линии - столбцы, красные - строки, подсоединенные к соответствующим пинам платы Ардуино. Скетч при этом выглядит таким образом. (см. напр. https://3d-diy.ru/wiki/arduino-moduli/elastichnaya-matrichnaya-klaviatura-4x4/ и др. интернеты)

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");

Пины строк конфигурируются как выходы с высоким уровнем, пины столбцов - как входы с подтяжкой к высокому уровню. Далее в цикле Loop, с периодичностью 50 мс на пины строк в цикле последовательно выставляется низкий уровень, а во вложенном цикле опрашиваются пины столбцов. Если на столбце окажется низкий уровень (так-то он подтянут к высокому), то значит строка i замкнута со столбцом j, и соответственно нажата клавиша k4x4(i,j)
Все прекрасно работает, но есть пара моментов.

1. Если пользователь нажмет одновременно две кнопки в одной строке, то в процессе опроса выход с высоким уровнем окажется закорочен на выход с низким уровнем, что может привести к повреждению платы. Для предотвращения замыкания обычно ставят защитные диоды, что несколько усложняет схему.

2. Может оказаться, что потребуется отловить нажатие кнопки внутри цикла, выполняющегося внутри процедуры loop. Указанный выше способ не позволит это сделать, так как кнопки не будут опрошены, пока не завершится этот внутренний цикл.

Для устранения указанных выше недостатков предлагается следующий вариант подключения клавиатуры и обработки нажатия кнопок с использованием прерываний.

(Как справедливо указали в комментариях, указанный метод подходит только для тех плат, у которых можно привязать прерывания на требуемое количество пинов, к которым подключаются столбцы клавиатуры, в частности Arduino Due, Mega2560 и т.п.)

Пины строк устанавливаются в режим выхода с низким уровнем. Пины столбцов устанавливаются в режим входа с подтяжкой к высокому уровню. К пинам столбцов привязываются прерывания, срабатывающие по спаду сигнала.

attachInterrupt(5, irqkb5, FALLING);

При нажатии кнопки вход столбца замыкается с выходом строки с низким уровнем, уровень на этом входе падает и вызывается процедура обработки прерывания, в которой запоминается номер столбца. Например для столбца 5

void irqkb5 () {kbcol=5;}

После этого вызывается процедура определения строки. По сути она такая же, как в варианте с опросом - на строки последовательно устанавливается высокий уровень и проверяется, не появится ли высокий уровень на нашем столбце, вызвавшем прерывание. Таким образом мы получим номер столбца и строки и, соответственно, наименование нажатой кнопки. Единственно, для предотвращения замыкания выходов, высокий уровень на строке устанавливается не переводом выхода в HIGH, а переключением в режим входа с подтяжкой.

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

После выполнения процедуры определения нажатой кнопки, на строках восстанавливается низкий уровень, и к пинам столбцов снова подключаются прерывания. Для предотвращения влияния дребезга добавлены задержки 50 мс после прерывания и цикл ожидания высокого уровня после отпускания кнопки.

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

С использованием данного способа, можно отловить нажатие кнопки (почти) в любой момент выполнения программы, в том числе внутри любого встроенного цикла. Для этого достаточно внутри цикла вставить проверку флага (номера столбца, запомненного в прерывании) и по результату принять решение о том или ином действии.

Исходный код скетча (для Arduino Due) приведен ниже.

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)
}

После обработки прерывания (запоминания номера столбца), прерывания отключаются,

detachInterrupt(5); //

а после завершения процедуры определения нажатой кнопки - включаются обратно.
Для прерывания встроенного цикла в его начало можно вставить например такое условие

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

Таким образом нажатие любой кнопки приведет к выходу из цикла.

Tags:
Hubs:
Total votes 9: ↑6 and ↓3+3
Comments37

Articles