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

 

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

      Клавиатуры с механическими кнопками(как и любые другие ключевые/кнопочные устройства) имеют одну неприятную особенность. А именно, так называемый дребезг контактов... Грубо говоря, если не отфильтровать этот дребезг, то вместо одного нажатия кнопки, программа будет фиксировать несколько быстрых нажатий. Такое событие может негативно повлиять на заданный технологический процесс которому привязана нажимаемая кнопка. К примеру, по нажатию кнопки работает программный счетчик, инкрементирующий какое либо значение на единицу с выводом результата на дисплей. Если событие нажатия не имеет программную фильтрацию, то и счетчик будет инкрементироваться не на единицу за одно нажатие, а на несколько единиц. В итоге на дисплей будут выведены неверные данные, такая программа никуда не годится. Как уже было сказано, нужно принять полезный сигнал и отсеять бесполезный... Сделаем это программно! Для этого нам потребуется(с заделом на будущее) создать и описать объект класса, в котором будут реализованы следующие функции:

  1. Функция события нажатия любой из кнопок
  2. Функция обработки дребезга контактов
  3. Функция возврата кода нажатой кнопки

      Сразу скажу, в программе не будут использоваться какие либо указатели или ссылки, использование класса на С++ будет с самым простым и базовым использованием насколько это возможно. Весь приведённый дальнейший код адаптирован именно под твой неокрепший ум, так что вперёд. Пояснять строки кода я буду не по порядку, сначала разберём всё то что касается класса, потом вернёмся к функции setup() и loop(), в самом конце разберёмся с функцией конечной обработки нажатий myKeypadPressEvent(). Полную версию исходного кода скетча просмотреть и скачать можно здесь. 

Секция #define

/* Пин подключения клавиатурного модуля */
#define KEYPAD_PIN A0

/* Переопределение косвенных кодов кнопок аналоговой клавиатуры */
#define KEY0 0
#define KEY1_F1 1
#define KEY2_UP 2
#define KEY3_F2 3
#define KEY4_LEFT 4
#define KEY5_OK 5
#define KEY6_RIGHT 6
#define KEY7_F3 7
#define KEY8_DOWN 8
#define KEY9_F4 9
#define KEY_ENT 10
#define KEY_ESC 11
#define KEY_NONE 12 //Когда ни одна кнопка не нажата

 

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

#define KEY_NONE

 

Секция прототипа класса class Keypad3x4 + создание объекта класса

/* Объявляем прототип объекта класса */
class Keypad3x4
{
    /* Пин подключения */
    int Pin;
    /* Контроль состояния кнопок, подавляем дребезг */
    int currentButtonState, previousButtonState;
    /* Маркер нажатия */
    bool KeyIsPressed = false;
    /* Подавление дребезга контактов */
    int DebounceKey(int Code, int _last);
    public:
    /* Конструктор класса */
    Keypad3x4(int _pin);
    /* Обработчик события нажатия */
    bool KeyPressed();
    /* Возвращаем код нажатой кнопки */
    int getKeyCode();
};

/* Создаём объект myKeypad класса Keypad3x4 */
/* Передаём в конструктор номер пина подключения */
Keypad3x4 myKeypad(KEYPAD_PIN);

 

        Здесь собственно и объявляется прототип класса Keypad3x4 с функциями и данными членами класса. Разберёмся подробнее. В private секции класса есть переменная Pin которая будет хранить номер пина подключения клавиатурного модуля. Далее видим переменные currentButtonState и previousButtonState в них будем хранить состояния кнопок - текущее и предыдущее, эти переменные нужны для функции обработчика дребезга контактов DebounceKey(). Маркер нажатия KeyIsPressed сигнализирует о том что произошло нажатие одной из кнопок. Также, здесь прописана сама функция подавления дребезга DebounceKey().

        В public секции класса соответственно есть конструктор класса Keypad3x4(int _pin) с принимаемым параметром, обработчик события нажатия KeyPressed() который вернёт состояние маркера KeyIsPressed, если было нажатие и функция возврата кода нажатой кнопки getKeyCode(). После описания прототипа класса создаём обект класса, передаём в конструктор номер пина подключения.

 

Секция функции KeyPressed()

/* Обработчик событий нажатия - возвращаем состояние маркера KeyIsPressed */
bool Keypad3x4::KeyPressed()
{
    currentButtonState = DebounceKey(getKeyCode(), previousButtonState);
    if(currentButtonState != KEY_NONE)
    {
        if(currentButtonState != previousButtonState)
            KeyIsPressed = true;
        else
            KeyIsPressed = false;
    }
    previousButtonState = currentButtonState;
    return KeyIsPressed;
}

 

        В качестве возвращаемого значения эта функция использует тип данных bool, сама же функция возвращает состояние маркера KeyIsPressed, который соответственно может принимать два значения true или false(нажата/не нажата). Сам маркер сигнализирует лишь о том что событие нажатия либо было, либо его не было. Внутри данной функции запускается функция обработчик дребезга DebounceKey(). Именно по результату работы функции DebounceKey() принимается условие установки маркера KeyIsPressed.

 

Секция функции DebounceKey()

/* Подавление дребезга контактов при нажатии кнопок */
int Keypad3x4::DebounceKey(int Code, int _lastValue)
{
    int _nextValue = getKeyCode();
    if(_nextValue != _lastValue)
    {
        delay(15);
        _nextValue = getKeyCode();
    }
return _nextValue;
}

 

        Функция принимает в качестве параметров код нажатой кнопки, предварительно вызвав функцию getKeyCode() и предыдущее состояние кнопки из переменной previousButtonState. Возвращает эта функция также код нажатой кнопки, в теле самой функции реализована задержка в 15 миллисекунд, для получения устойчивого состояния кнопки если она была нажата.

 

Секция функции getKeyCode()

    /* Получение кода нажатой кнопки */
    int Keypad3x4::getKeyCode()
    {
        int buttonValue = analogRead(Pin);

    if(buttonValue < 480)
        return KEY_NONE;
    else if(buttonValue < 500 && buttonValue > 480)
        return KEY_ESC;
    else if(buttonValue < 520 && buttonValue > 500)
        return KEY0;
    else if(buttonValue < 550 && buttonValue > 530)
        return KEY_ENT;
    else if(buttonValue < 580 && buttonValue > 560)
        return KEY3_F2;
    else if(buttonValue < 610 && buttonValue > 590)
        return KEY6_RIGHT;
    else if(buttonValue < 650 && buttonValue > 630)
        return KEY9_F4;
    else if(buttonValue < 690 && buttonValue > 670)
        return KEY2_UP; 
    else if(buttonValue < 740 && buttonValue > 720)
        return KEY5_OK; 
    else if(buttonValue < 800 && buttonValue > 780)
        return KEY8_DOWN; 
    else if(buttonValue < 860 && buttonValue > 840)
        return KEY1_F1; 
    else if(buttonValue < 940 && buttonValue > 920)
        return KEY4_LEFT; 
    else if(buttonValue < 1024 && buttonValue > 1015)
        return KEY7_F3;
}

 

   Здесь всё просто, записываем в переменную buttonValue значение считываемое из аналогового порта А0 и в соответствии с полученным, возвращаем одно из значений предопределённых констант из секции #define.

 

Секция функций setup(), loop() и myKeypadPressEvent()

void setup()
{
    /* Открываем соединение для монитора порта */
    Serial.begin(9600);
}

void loop()
{
    /* Если было событие нажатия - вызываем обработчик, передаём код нажатой кнопки */
    if(myKeypad.KeyPressed())
        myKeypadPressEvent(myKeypad.getKeyCode());
}

/* Функция обработчик событий нажатия */
void myKeypadPressEvent(uint8_t KeyCode)
{
    switch(KeyCode)
    {
        case KEY0:
            Serial.println("Была нажата кнопка 0");
            break;
        case KEY1_F1:
            Serial.println("Была нажата кнопка 1 или F1");
            break;
        case KEY2_UP:
            Serial.println("Была нажата кнопка 2 или UP");
            break;
        case KEY3_F2:
            Serial.println("Была нажата кнопка 3 или F2");
            break;
        case KEY4_LEFT:
            Serial.println("Была нажата кнопка 4 или LEFT");
            break;
        case KEY5_OK:
            Serial.println("Была нажата кнопка 5 или OK");
            break;
        case KEY6_RIGHT:
            Serial.println("Была нажата кнопка 6 или RIGHT");
            break;
        case KEY7_F3:
            Serial.println("Была нажата кнопка 7 или F3");
            break;
        case KEY8_DOWN:
            Serial.println("Была нажата кнопка 8 или DOWN");
            break;
        case KEY9_F4:
            Serial.println("Была нажата кнопка 9 или F4");
            break;
        case KEY_ENT:
            Serial.println("Была нажата кнопка ENT");
            break;
        case KEY_ESC:
            Serial.println("Была нажата кнопка ESC");
            break;
    }
}

 

      В функции setup() я думаю всё предельно ясно, в функции loop() имеется одно единственное условие, которое проверяется каждый раз по ходу выполнения программы. Этим условием вызывается вышеописанная функция KeyPressed(), но вызывается она уже у созданного объекта класса myKeypad. То есть, идёт опрос объекта класса через функцию которая должна вернуть состояние маркера KeyIsPressed. Если условие выполняется(KeyIsPressed == true), то далее вызывается функция обработчик нажатий myKeypadPressEvent(), с передачей в неё кода кнопки, который в свою очередь возвращает объект класса myKeypad через вызов своей функции члена getKeyCode().

      В теле функции myKeypadPressEvent() реализовано условие switch-case, функция принимает в качестве параметра код кнопки и проверяет на соответствие предопределённым константам указанным в секции #define. В зависимости от полученного результата в монитор порта выводится строка с сообщением. Говоря простым языком, работа программы заключена в нескольких действиях:

  1. Понять было нажатие или нет
  2. Если было, то отфильтровать дребезг и вернуть код нажатой кнопки
  3. Вывести в монитор порта однократное сообщение о событии

       При этом, в мониторе порта уже не будет видно никаких лишних цифр и значений, быстро заполняющих всё пространство. Только однократные сообщения о событии нажатия. Здесь нужно конечно уточнить, что монитор порта в данном случае используется как инструмент для проверки событий нажатия. По сути, этот скетч уже можно использовать в каком либо проекте, достаточно убрать инструкции Serial библиотеки, закомментировав их. Вместо этих инструкций можно поставить уже свои, более функциональные и адаптированные под какой то свой проект. Можно сделать даже многоуровневое меню, об этом возможно будет отдельная статья, но прежде чем браться за реализацию меню, хотелось бы реализовать библиотеку с классом Keypad3x4. Согласись, удобно не видеть вышеописанный код вставив весь его функционал в качестве подключаемой библиотеки? Именно этим я и планирую заняться в следующей статье, если ты дочитал до этого места, то обязательно переходи по ссылке для прочтения 3-й части.