Урок 18. Подключение матрицы кнопок к Ардуино. Функция tone().

Arduino UNO R3

Научимся подключать к плате Ардуино матрицу кнопок, использовать для генерации звука стандартную функцию tone().

Предыдущий урок     Список уроков     Следующий урок

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

 

Существует другой способ подключения кнопок к плате Ардуино – объединение кнопок в матрицу.

Подключение матрицы кнопок к микроконтроллеру.

Вот пример схемы такой матрицы.  Для подключения 16 кнопок требуется только 8 выводов микроконтроллера.

Схема матрицы кнопокКнопки подключены к вертикальным и горизонтальным линиям матрицы. Состояние кнопок для каждой вертикальной линии проверяется отдельно. На вертикальную линию подается сигнал высокого уровня (5 В) и считываются состояния горизонтальных линий. Высокий уровень в горизонтальной линии покажет, что соответствующая кнопка нажата. Далее проверяются остальные вертикальные линии.

Резисторы обеспечивают нулевое напряжение на входах микроконтроллера при разомкнутых кнопках.

Такая схема часто используется для подключения матрицы кнопок к плате Ардуино в практических приложениях. Что меня удивляет. Ведь эта схема работает некорректно при одновременном нажатии двух и более кнопок из одной горизонтальной линии. В этом случае:

  • Результат будет непредсказуемым. Через горизонтальную линию и две нажатые кнопки замкнутся вертикальные линии с высоким и низким уровнями сигнала. Какая вертикальная линия перетянет, такой уровень и будет на горизонтальной линии.
  • Произойдет замыкание выводов микроконтроллера с разными уровнями сигналов. В лучшем случае повысится потребляемый ток, и микроконтроллер будет нагреваться.

Схема замыкания

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

Схема замыкания

 

Подключение матрицы кнопок к плате Ардуино.

Подключим матрицу кнопок 3x4 к плате Ардуино по такой схеме.

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

Подключение матрицы кнопок к Ардуино

Класс сканирования матрицы кнопок.

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

Класс должен:

  • определять текущее состояние каждой кнопки;
  • вырабатывать признаки “была нажата” для каждой кнопки (клики);
  • устранять дребезг контактов кнопок.

Я приведу сразу исходный текст класса MatrixKeys. Как он работает понятно из комментариев.

// описание класса сканирования матрицы кнопок
class MatrixKeys  {
  public:
    boolean flagPress[4][4]; // признаки кнопка в нажатом состоянии
    boolean flagClick[4][4]; // признаки нажатия кнопки (клик)
    void  scanState();    // метод проверки состояния кнопок
    // конструктор
    MatrixKeys(byte verticalPin1, byte verticalPin2, byte verticalPin3, byte verticalPin4,
               byte horizontalPin1, byte horizontalPin2, byte horizontalPin3, byte horizontalPin4,
               byte numAckn);   
  private:
    byte  _verticalPins[4];   // выводы вертикальных линий
    byte  _horizontalPins[4]; // выводы горизонтальных линий
    byte  _buttonCount[4][4]; // счетчики подтверждения состояния кнопок
    byte _numAckn;          // число подтверждений состояния кнопок
    byte  _scanVertLine;    // номер сканируемой вертикальной линии
    byte  _scanHorizLine;    // номер сканируемой горизонтальной линии
} ;

 

//---------------------------- конструктор
MatrixKeys::MatrixKeys(byte verticalPin1, byte verticalPin2, byte verticalPin3, byte verticalPin4,
                       byte horizontalPin1, byte horizontalPin2, byte horizontalPin3, byte horizontalPin4,
                       byte numAckn)  {

  // загрузка массивов выводов
  _verticalPins[0]= verticalPin1; _verticalPins[1]= verticalPin2; _verticalPins[2]= verticalPin3; _verticalPins[3]= verticalPin4;
  _horizontalPins[0]= horizontalPin1; _horizontalPins[1]= horizontalPin2; _horizontalPins[2]= horizontalPin3; _horizontalPins[3]= horizontalPin4;

  // перегрузка остальных аргументов
  _numAckn= numAckn;
 
  // инициализация выводов матрицы
  byte i= 0;
  while (i < 4) {

    if ( _verticalPins[i] != 255 ) {    // если вывод не отключен   
      pinMode(_verticalPins[i], OUTPUT);
      digitalWrite(_verticalPins[i], LOW);
    } 

    if ( _horizontalPins[i] != 255 )     // если вывод не отключен   
      pinMode(_horizontalPins[i], INPUT);
   
    i++;       
  }

    _scanVertLine= 0;   // номер сканируемой вертикальной линии
    _scanHorizLine= 0;  // номер сканируемой горизонтальной линии

  // сброс всех признаков
    i= 0; while (i < 3) {
            flagPress[i][0]= false; flagPress[i][1]= false; flagPress[i][2]= false; flagPress[i][3]= false;
            flagClick[i][0]= false; flagClick[i][1]= false; flagClick[i][2]= false; flagClick[i][3]= false;          
            _buttonCount[i][0]= 0; _buttonCount[i][1]= 0; _buttonCount[i][2]= 0; _buttonCount[i][3]= 0;
            i++;
          }                          
  }
 
//-------------------------------- метод проверки состояния кнопок
// при нажатой кнопке flagPress= true
// при отжатой кнопке flagPress= false
// при нажатии на кнопку flagClick= true
void  MatrixKeys::scanState() {

  if ( _verticalPins[_scanVertLine] != 255 ) {    // если вывод не отключен   

  // опрос кнопок горизонтальных линий
  _scanHorizLine= 0;  while ( _scanHorizLine < 4 ) {
   
 if ( flagPress[_scanVertLine][_scanHorizLine] == digitalRead(_horizontalPins[_scanHorizLine]) )
    //  состояние кнопки осталось прежним
    _buttonCount[_scanVertLine][_scanHorizLine]= 0;  // сброс счетчика подтверждений

  else  {
     // состояние кнопки изменилось
     _buttonCount[_scanVertLine][_scanHorizLine]++;   // +1 к счетчику подтверждений

    if ( _buttonCount[_scanVertLine][_scanHorizLine] >= _numAckn ) {
      // состояние кнопки стало устойчивым
      flagPress[_scanVertLine][_scanHorizLine]= ! flagPress[_scanVertLine][_scanHorizLine]; // инверсия признака состояния
     _buttonCount[_scanVertLine][_scanHorizLine]= 0;  // сброс счетчика подтверждений

      if ( flagPress[_scanVertLine][_scanHorizLine] == true )
        flagClick[_scanVertLine][_scanHorizLine]= true; // признак клика кнопки            
     }      
  }
  _scanHorizLine++; }
 
  }

  // установка следующей вертикальной линии 
    digitalWrite(_verticalPins[_scanVertLine], LOW);
    _scanVertLine++; if (_scanVertLine >3) _scanVertLine= 0;
    digitalWrite(_verticalPins[_scanVertLine], HIGH);        
}

Для устранения дребезга кнопок используется способ ожидания стабильного состояния контактов, описанный в уроке 6. В параллельном процессе должен регулярно вызываться метод scanState().

В результате формируются признаки массивов flagPress[4][4] и flagClick[4][4]:

  • при нажатой кнопке flagPress= true;
  • при отжатой кнопке flagPress= false;
  • при нажатии на кнопку flagClick= true.

Объект типа MatrixKeys при создании имеет параметры:

  • номера выводов подключения вертикальных линий матрицы 1, 2, 3 ,4;
  • номера выводов подключения горизонтальных линий матрицы 1, 2, 3 ,4;
  • число подтверждений состояния контактов.

Пример создания объекта:

// создаем объект матрица кнопок keys
// подключаем вертикальные линии к выводам 9, 10, 11, 12
// подключаем горизонтальные линии к выводам 4, 5, 6, 7
// число подтверждений состояния контактов 6
MatrixKeys  keys(9, 10, 11, 12, 4, 5, 6, 7, 6);

Конкретные выводы матрицы можно отключить, задав для них в конструкторе номера  равные 255. Это может потребоваться при подключении матрицы меньшей размерности.

Для генерации звука по нажатию кнопок можно использовать стандартную функцию tone().

 

Стандартная функция генерации звука tone().

Функция генерирует на заданном выводе сигнал прямоугольной формы.

void tone(pin, frequency)
void tone(pin, frequency, duration)

Аргументы:

  • pin – номер вывода;
  • frequency – частота сигнала в Гц;
  • duration – длительность сигнала в миллисекундах.

Если длительность сигнала не задана третьим аргументом, то  сигнал вырабатывается до тех пор пока не будет вызвана функция noTone().

Необходимо помнить, что для генерации сигнала функция tone() использует Таймер 2 платы Ардуино. Поэтому, если этот таймер уже используется в программе, например для формирования прерывания, то функция tone() приведет к конфликту обращения к таймеру 2. По этой причине я не создал в классе возможности сопровождения нажатия кнопок звуковым сигналом. Лучше это желать вне класса, используя в каждом конкретном случае свой способ.

 

Библиотека сканирования матричной клавиатуры 4 x 4.

Я оформил класс MatrixKeys библиотекой. Загрузить ее можно по этой ссылке MatrixKeys.h. Как установить библиотеку написано в уроке 9.

В отличие от известной библиотеки Keypad эта библиотека:

  • значительно компактнее и быстрее;
  • ориентирована на использование в параллельном процессе;
  • надежно обрабатывает дребезг контактов, позволяет задавать число подтверждений состояния кнопок.

Напишем простую программу для проверки и демонстрации библиотеки  MatrixKeys.h. Программа передает на компьютер по последовательному порту состояние матрицы кнопок:

  • нажатая кнопка отображается как ”*”;
  • отжатая – ”.”;
  • момент нажатия кнопки (клик) отображается символом ”=” в течение 0,5 секунд.

// матрица кнопок
#include <MatrixKeys.h>
#include <MsTimer2.h>

// создаем объект матрица кнопок keys
// подключаем вертикальные линии к выводам 9, 10, 11, 12
// подключаем горизонтальные линии к выводам 4, 5, 6, 7
// число подтверждений состояния контактов 6
MatrixKeys  keys(9, 10, 11, 12, 4, 5, 6, 7, 6);

void setup() {
  Serial.begin(9600); // инициализируем порт, скорость 9600
  MsTimer2::set(2, timerInterrupt); // задаем период прерывания по таймеру 2 мс
  MsTimer2::start();               // разрешаем прерывание по таймеру
}

void loop() {

  // перебор строк
  for (int i = 0; i < 4; i++) {
   
    // перебор столбцов
    for (int j = 0; j < 4; j++) {

      if (keys.flagClick[j][i] == true) { Serial.print("="); keys.flagClick[j][i]=false; tone(8,1000,50); }
      else { if (keys.flagPress[j][i] == true) Serial.print("*"); else Serial.print("."); }     
    }      
      Serial.println(" ");    
  }

  Serial.println(" ");
  delay(500);
}

//-------------------------------------- обработчик прерывания 2 мс
void  timerInterrupt() {
  keys.scanState(); // сканирование матрицы
}

Загрузите программу в плату. Не забудьте установить библиотеки MatrixKeys.h и MsTimer2.h. Откройте монитор последовательного порта. На экране, каждые 0,5 секунды, будут пробегать блоки состояния матрицы.

Окно монитора порта

Нажимая на кнопки матрицы можно проверить работу программы.

Если необходимо передавать код нажатой клавиши, то можно преобразовать состояние массива признаков кликов keys.flagClick[4][4] в коды кнопок  codKeys[4][4].

// матрица кнопок
#include <MatrixKeys.h>
#include <MsTimer2.h>

// массив кодов кнопок
const char codKeys[4][4] = 
{ {'1', '4', '7', '*'},
  {'2', '5', '8', '0' },
  {'3', '6', '9', '#'},
  {' ', ' ', ' ', ' '}
};

// создаем объект матрица кнопок keys
// подключаем вертикальные линии к выводам 9, 10, 11, 12
// подключаем горизонтальные линии к выводам 4, 5, 6, 7
// число подтверждений состояния контактов 6
MatrixKeys  keys(9, 10, 11, 12, 4, 5, 6, 7, 6);

void setup() {
  Serial.begin(9600); // инициализируем порт, скорость 9600
  MsTimer2::set(2, timerInterrupt); // задаем период прерывания по таймеру 2 мс
  MsTimer2::start();               // разрешаем прерывание по таймеру
}

void loop() {

  // вычисление кода нажатой кнопки
  // перебор столбцов
  for (int i = 0; i < 4; i++) {
    // перебор строк
    for (int j = 0; j < 4; j++) {
      if (keys.flagClick[i][j] == true) {
        keys.flagClick[i][j]=0; 
        Serial.println(codKeys[i][j]);
        } 
    }      
  }
}

//-------------------------------------- обработчик прерывания 2 мс
void  timerInterrupt() {
  keys.scanState(); // сканирование матрицы
}

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

Окно монитора порта

При необходимости Вы легко сможете переделать библиотеку на матрицу кнопок других размерностей.

 

В следующем уроке будем подключать к плате Ардуино семисегментные светодиодные индикаторы.

Предыдущий урок     Список уроков     Следующий урок

4 комментария на «Урок 18. Подключение матрицы кнопок к Ардуино. Функция tone().»

  1. Почему у 4 кнопки не обнуляем признак?:
    сброс всех признаков
    i= 0; while (i < 3){…i++}

    • Наверное, ошибка. Спасибо. В принципе, они обнуляются при создании переменных.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *