Матричная клавиатура - ввод с удержанием

Тема в разделе "Arduino & Shields", создана пользователем risele, 9 май 2017.

  1. risele

    risele Нерд

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

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

    Собственно, на текущий момент клавиатура честно ловит произвольное число одновременных нажатий. Однако, правильно сформировать буфер у меня не получается.

    Как это делается:
    В стандартном варианте, V вертикальных пинов мы конфигурируем на выход LOW, а H горизонтальных - на вход INPUT_PULLUP (тогда, если кнопка не нажата, то на них всегда будет 1 и никакие шумы этому не помеха). Затем, если какой-то из входных пинов стал 0 (т.е. кнопка, к нему подключенная, замкнулась), то мы перебираем все выходные пины, поочередно ставя их в 1 и ожидая, когда пину входному пину вернется его состояние. Однако, это позволяет отловить только единичные нажатия и комбинации нажатий в строке (если проверять не один входящий пин, а все).

    Я несколько модернизировал этот метод.
    0. Изначально вертикальные пины выставляются на выход с LOW, горизонтальные - на вход с подтяжкой.
    1. Затем мы проверяем, не стал ли хотя бы один пин на входе LOW. Если стал, то запускаем процедуру анализа.
    2. Пробегаем все входящие пины. Если значение пина - LOW, то переходим к пункту 3. Если нет, то переходим к следующему входящему пину.
    3. Поочередно выставляем на каком-то одном выходящем пине LOW, на остальных - HIGH. Если на текущем входящем пине мы поймали LOW, то это значит, что данная кнопка зажата и мы добавляем ее в буфер отмечаем для себя, что ее надо будет положить в буфер (о том, как это делается - позже). Если не нажата, то, возможно, она была отжата и надо бы проверить это дело и заполнить буфер
    4. После пробегания всех выходных пинов, восстанавливаем их положение в LOW.
    Такой подход позволяет отследить одновременные нажатия в строке.
    чтобы проследить одновременные нажатия в столбце, идем на хитрость:
    5. Меняем местами входящие и выходящие пины и повторяем процедуры 1-4.

    Это все работает.

    Как я пытаюсь сформировать буфер:
    В процессе поиска нажатой кнопки, если (см под катом) мы ловим ее номер (ButtonID). Затем проверяем, была ли она нажата ранее (читаем переменную _TimePresed[ButonID], в которую пишется время ее последнего нажатия. Если там 0, то она не была нажата). Если нажата не была, то записываем время первого нажатия. Если же была нажата, то возможны два варианта: короткое нажатие (>10мс, позволяет точно отсечь шумы) и длинное (у меня более 500мс). В соответствии с ними записываем в переменную _ButtStates[ButtonID] флаг нажата/удержана.
    Если в процессе поиска кнопки мы обнаружили, что кнопку отпустили, то добавляем символ кнопки в буфер нажатых кнопок Chars (и расширяем размер буфера charscount), а флаг нажата/удержана - в свой соответствующий буфер ButtonStates (и не расширяем размер буфера). И, разумеется, обнуляем таймер для кнопки: _TimePresed[ButonID]=0;

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

    Собственно, файл прилагаю ниже, а тут приведу фрагмент кода заполнения буфера.
    Комментарии,. выделенные тройным двойным слешем // // // обозначают не только места, где что не работает, но и немного объясняют, что можно изменить под себя.
    Объявление переменных, чтобы было понятно, о чем речь:

    Код (C++):
    char *Chars = new char[1] {'\0'}; //буфер полученных с клавиатуры символов
    bool *ButtonStates = new bool[0]; //буфер состояний Нажато(0)/Удержано(1) символов с клавиатуры
    unsigned long _TimePressed[16]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; //место временных маркеров нажатия кнопок
    bool _ButtStates[16]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; //Массив маркеров Нажато (0)/Удержано (1)
    short ButtonID; //номер нажатой кнопки

    unsigned long charscount = 0; //количество введенных символов (длина буфера)
    unsigned long CurTime=0; //текущее время
    unsigned long onPress = 10; //время удержания кнопки, после которого кнопка считается нажатой
    unsigned long onKeep = 500; //время удержания кнопки, после которого кнопка считается удержанной
    //Символы кнопок, пока тест на 1й (считая с нуля) строке
    char massCharKey[3][16] ={...} // большой массив соответствия номеров кнопок и их символов для всех клавиатур
    И, собственно, само заполнение буфера:
    Код (C++):
     if (!digitalRead(pinsKB[Input[InpPin]])) //Если кнопка нажата,
            {
    // // // Отладочный вывод
    Serial.print(massCharKey[massItem][ButtonID]);
    // // // ----- vvv ТУТ ТО ЧТО НЕПРАВИЛЬНО РАБОТАЕТ vvv ----- // // //
              CurTime = millis();       //И берем текущее время
              if (_TimePressed[ButtonID]==0) //Если кнопка не была нажата ранее
                _TimePressed[ButtonID]=CurTime; //стартуем таймер для неё
              else //Если же кнопка была нажата ранее,
                if (CurTime-_TimePressed[ButtonID]>onKeep) //Проверяем, держут ли её достаточно для состояния Удержание
                  _ButtStates[ButtonID]=1; //И, если да, поднимаем флаг удержания
                else  //Далее проверяем, держут ли её достаточно долго для состояния Нажатие
                  if (CurTime-_TimePressed[ButtonID]>onPress) //Если да, то ставим флаг нажатия.
                    _ButtStates[ButtonID]=0;
             }
             else //Если кнопка отпущена
             {
                if (_TimePressed[ButtonID]>0) //Но была нажата ранее
                  if (CurTime-_TimePressed[ButtonID]>onPress) //И держали дольше, чем время для состояния Нажатие (защита от шума)
                  {
                    addChar(Chars,massCharKey[massItem][ButtonID]);            //Добавляем символ кнопки в стек
                    addBool(ButtonStates,_ButtStates[ButtonID]);  //И флаг нажатие/удержание тоже добавляем
             
                    _TimePressed[ButtonID]=0;     //И обнуляем её время нажатия, симулируя свежую кнопку.
                  }
             }


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


    PS:
    В момент переконфигурации пинов снача выходные становятся входными (т.е. получают огромное входное сопротивление), и только потом входные становятся выходными и переходят в LOW. Вроде бы, это должно позволить избежать КЗ. Но рекомендовал бы подключать клавиатуру таки через резисторы, а то мало ли что. Я, лично, вообще собираюсь использовать её в прерываниях и подключить её через шумогосящую схему (RC+инв.триг.Ш) и воспользоваться микросхемой 4-И, чтобы аппаратно гененрировать прерывание на опрос (отмечал выше по тексту).

    Во-первых, можно вообще не создавать дополнительные переменные для разных комбинаций вход-выход.
    Во-вторых, в теле программы не обращаться к ним как pinsKB[Output[pin]], а заранее сконфигурировать эти массивы как набор пинов, а не контактов клавиатуры. Ну, так уж повелось.
    В-третьих, массив флагов нажато/удержано можно сделать не массивом boolean, а массивом byte[1], флаги ставить с помощью bitWrite и расширять его каждые 8 нажатий (не думаю, что он часто будет больше 1 байта).
     

    Вложения:

    • Keyboard.ino
      Размер файла:
      12,8 КБ
      Просмотров:
      192
    Последнее редактирование: 9 май 2017
    ИгорьК нравится это.
  2. qwone

    qwone Гик

    Ответьте для начала: зачем нужны кнопки в устройстве? Идиотский вопрос - разумеется управлять уствойсвом. Но ответ неверен. Нажатие на кнопки нужно для вызова функций с помощью которых можно управлять устройством. Для пользователя это одинаково. Но вы же пытаетесь быть программистом и это должно быть важно для вас.
    Идем дальше. Почему матричная клавиатура . Разумеется , при меньших задействованых выводах получается больше кнопок, а значит больше управляющих функций можно сделать. Клавиатура 3х4 = 7 выводов 12 управляющих функций. В принципе меню это тоже как сократить количество кнопок и увеличить количество управляющих функций.
    И наконец вы пытаетесь регистрировать одновременное нажатие клавиш. Для чего? Да блин до того же. Увеличить кол-во управляемых функций.
    Теперь я веду к чему. В вашей программе должны быть эти функции. Разумется можно вызывать их прямо, а можно через указатели. Дальше вам не надо делать индивидуальный скан кнопок . Клавиатура сканируется сразу и заносом в память. А вот дальше происходит анализ скана и вызов нужных функций- функции которые вызываются от индивидуальных нажатий, и функции по нажатию нескольких.
     
    Aleksander1997 нравится это.
  3. rkit

    rkit Гуру

    Чудовищно много текста и кода для такой простой задачи, еще и какие-то непонятные секретные костыли с микрухами, наверняка не нужными. Вникать во все это особого желания нету.
    Простейшая регистрация millis() в момент нажатия и антиотскок дадут и факт нажатия, и продолжительность, и кода не более чем на 15 строк.
     
    ostrov нравится это.
  4. ostrov

    ostrov Гуру

    Текст не осилил.