Перейти к содержанию
    

Динамический опрос клавиатуры

Приветствую всех!

Я увлекаюсь программированием МК на уровне хобби. В данный момент делаю устройство с выводом инфы на семисегментных индикаторах и вводом через матричную клавиатуру 4x4. С динамической индикацией у меня сложностей нет, а вот с вводом возникли затруднения. Описываю как делал.

Динамическая индикация и ввод цифр производятся в одном обработчике прерывания таймера. Перебираем столбцы и строки клавиатуры. Если на считывающем порте клавиатуры замечено нажатие, то сохраняем номер столбца и строки в отдельный массив. При следующих циклах опроса проверяем эту кнопку на дребезг. Антидребезг я позаимствовал из статьи Jack G. Ganssle "A Guide to Debouncing" (упоминания об этом методе я встречал довольно часто на форумах). Когда дребезг прошел, заносим цифру, соответствующую клавише в отдельную переменную и взводим флаг. В основном цикле проверяем этот флаг: если взведен и на считывающем порту все единицы (т.е. кнопка уже отпущена), то отправляем цифру "склеиваться" из BCD-кода в переменную long (с этим у меня вопросов нет). 

void KeyScan (void)
{
   P1 = P1_Out[index];
   for(row=0; row<4; row++){
      if((P1 & P1_InputMask[row]) == 0){
         RawKey[0] = row;
         RawKey[1] = index;        
         }
      if(DebounceSwitch ()){
         NewKey = 1;
         }
      }
}

_Bool DebounceSwitch ()
{  
   static unsigned int State = 0;
   if((row == RawKey[0]) && (index == RawKey[1])){
      State = (State<<1) | !(RawKeyPressed ()) | 0xE000;
      if(State == 0xF000){
         temp = KeyTable[row][index];
         return 1;
         }
      }
   return 0; 
}

_Bool RawKeyPressed ()
{
   if((P1 & P1_InputMask[RawKey[0]]) == 0){
      return 1;
      }
   return 0;          
}

Вот так опрос выглядит в виде кода. В железе все работает правильно, но у меня сомнения в плане оптимальности. Не лишнего ли функций у меня в обработчике прерывания получилось? Можно ли как-то упростить этот процесс?

Благодарю за помощь. :smile:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

О какой оптимальности может идти речь вне контекста конкретного МК?:smile:

Ну а в целом - лично я не сторонник обрабатывать клавиатуру в прерывании.

Проще где-то в основном цикле или в периодическом процессе низкоприоритетной задачи RTOS.

Вот моя реализация под ARM Cortex-M3. Функция getRowSetCol() считывает значение строки, а затем устанавливает новый столбец

Скрытый текст

static u32 getRowSetCol(u32 colNum)
{
  const u32 idra = GPIOA->IDR,
            idrc = GPIOC->IDR;
  const struct
  {
    volatile u32 *bsrr;
             u32  pin;
  }colTbl[] =
  {
    {&GPIOB->BSRR, B12}, {&GPIOB->BSRR, B13}, {&GPIOB->BSRR, B14},
    {&GPIOB->BSRR, B15}, {&GPIOC->BSRR, B6},  {&GPIOC->BSRR, B3},
    {&GPIOA->BSRR, B1},  {&GPIOA->BSRR, B2},  {&GPIOA->BSRR, B3},
    {&GPIOA->BSRR, B4},  {&GPIOA->BSRR, B5}
  };
  
  GPIOA->BSRR = (B5  | B4  | B3  | B2 | B1) << 16;
  GPIOB->BSRR = (B15 | B14 | B13 | B12)     << 16;
  GPIOC->BSRR = (B6  | B3)                  << 16;
  if(colNum <= 10)
    *colTbl[colNum].bsrr = colTbl[colNum].pin;
  
  return idrc << 16 | idra;
}

А вот gpio_KeypadHandler() я вызываю в суперцикле

Скрытый текст

static sBtnState BtnState;

void gpio_KeypadHandler(void)
{
  static u32 curColNum = 0, row[11] = {0};
         u32 nxtColNum = curColNum  +  1;
  if(nxtColNum == arraydepth(row))
    nxtColNum = 0;
  
  row[curColNum] &= getRowSetCol(nxtColNum);
  
  if((curColNum = nxtColNum) == 0)
  {
    static u32 oldTickCnt = 0;
           u32 curTickCnt = time_GetTickCnt();
    if(curTickCnt - oldTickCnt >= 20)
    {
      oldTickCnt = curTickCnt;
      
      BtnState._0   = row[0]  >>  8 & B0;
      BtnState._1   = row[0]  >>  9 & B0;
      BtnState._2   = row[1]  >>  9 & B0;
      BtnState._3   = row[2]  >>  9 & B0;
      BtnState._4   = row[0]  >> 23 & B0;
      BtnState._5   = row[1]  >> 23 & B0;
      BtnState._6   = row[2]  >> 23 & B0;
      BtnState._7   = row[0]  >> 24 & B0;
      BtnState._8   = row[1]  >> 24 & B0;
      BtnState._9   = row[2]  >> 24 & B0;
      BtnState.min  = row[3]  >> 24 & B0;
      BtnState.pls  = row[3]  >> 23 & B0;
      BtnState.esc  = row[4]  >> 24 & B0;
      BtnState.home = row[4]  >> 23 & B0;
      BtnState.end  = row[4]  >>  9 & B0;
      BtnState.del  = row[2]  >>  8 & B0;
      BtnState.ent  = row[3]  >>  8 & B0;
      BtnState.pup  = row[4]  >>  8 & B0;
      BtnState.pdwn = row[4]  >> 25 & B0;
      BtnState.lft  = row[0]  >> 25 & B0;
      BtnState.up   = row[1]  >> 25 & B0;
      BtnState.dwn  = row[2]  >> 25 & B0;
      BtnState.rgh  = row[3]  >> 25 & B0;
      BtnState.f1   = row[6]  >> 17 & B0;
      BtnState.f2   = row[6]  >> 16 & B0;
      BtnState.f3   = row[7]  >> 18 & B0;
      BtnState.f4   = row[7]  >> 17 & B0;
      BtnState.f5   = row[7]  >> 16 & B0;
      BtnState.f6   = row[8]  >> 18 & B0;
      BtnState.f7   = row[8]  >> 17 & B0;
      BtnState.f8   = row[8]  >> 16 & B0;
      BtnState.a    = row[9]  >> 20 & B0;
      BtnState.b    = row[9]  >>  7 & B0;
      BtnState.c    = row[9]  >>  6 & B0;
      BtnState.d    = row[10] >> 20 & B0;
      BtnState.e    = row[10] >>  7 & B0;
      BtnState.f    = row[10] >>  6 & B0;
      BtnState.bmin = row[5]  >> 18 & B0;
      BtnState.bpls = row[6]  >> 18 & B0;
      
      for(u32 i = 0; i < arraydepth(row); ++i)
        row[i] = 0xFFFFFFFF;
    }
  }
}

Она проходится по всем столбцам и маской по Си-шному "побитовому И" производит фильтрацию ложных значений (помехи, дребезг и т.д.). Когда в течение 20 мс все значения битов "устаканились", кнопка считается действительно нажатой. И поэтому происходит перепаковка актуальных значений в нужный вид. И да - в моем случае матрица 11x11 (но кнопки там не везде) - сути это не меняет. Подтяжка пинов строки - к лог. 1. Это важно. "Бегает" тоже лог. 1. В принципе, подтягивать можно куда угодно, только код немного нужно будет подправить.

Ваша реализация, коль работает верно, нуждается ли в оптимизациях?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

9 минут назад, Arlleex сказал:

вне контекста конкретного МК

У меня Silabs C8051F310 используется. Так что РТОС здесь отпадает. )))

А за пример кода спасибо, попробую разобраться. 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

ARM Cortex-M3.


добрые нынче "гуру" пошли: всего лишь малышка C8051F310 "через соломинку надувают" ((-8Ж

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

13 часов назад, Evgeny сказал:

У меня Silabs C8051F310 используется. Так что РТОС здесь отпадает. )))

А за пример кода спасибо, попробую разобраться. 

Насколько все оптимально обычно понятнее после компиляции и пошаговой отладки.

Насколько оправданно рвать программу прерываниями - это тоже привязано к программе и устройству.

В некоторых случаях полезно делать контроллер клавиатуры на отдельной микросхеме или CPLD.

Но это для более серьезных клавиатур, например 8 на 8 или клавиатура+джойстик(манипулятор).

Я обычно клавиатуру 4 на 4 через прерывания не обслуживаю, но у меня своя специфика:

В моих разработках управление всем остальным происходит только после нажатия кнопок(клавиш).

Изменено пользователем byRAM

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

А я реализую опрос кнопок внутри суперлупа - запуском соответствующей функции. Антидребезг реализовал так: каждая кнопка описывается структурой, одним из членов которой является счетчик условных миллисекунд. Как только кнопка меняет состояние (PRESS->RELEASE или обратно), это указывается в структуре, а счетчик устанавливается на текущее системное время. И в течение заданных 20÷50мс как бы состояние не менялось, обработчик на это не реагирует. Ну, а дальше, скажем, если через 150÷300мс кнопка еще нажата, устанавливается состояние HOLD. Можно еще в этой же структуре хранить предыдущее на момент вызова геттера состояние (вдруг понадобится).

В итоге, вызвав геттер состояния кнопки, получаем ее текущее состояние, время изменения и предыдущее. И можно принять решение, что делать...

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 hours ago, Eddy_Em said:

. . .  Можно еще в этой же структуре хранить предыдущее на момент вызова геттера состояние (вдруг понадобится). . . .

по "геттеру" если можно разъясните. Скорее всего от get. А то у меня жестокая ассоциация с ламповой техникой-технологией :))))

---

По клваиатурно-клавишным делам все зависит от требований/активности использования клавиатурного ввода.

Для приложений с большим объемом ввода/использования клавиатуры имеет смысл ориентироваться на "событийный" режим ее работы

и соответствующий драйвер для этого функционала. Будут ли все ф-ии и возможности использоваться - другой вопрос. 

- отслеживания события "нажатие"

- отслеживание события "отжатие"

- отслеживание события "удержание"

- нажатие на одном ряду (строке) нескольких кнопок одновременно (аналог функции SHIFT)

- буферизация ввода

- скан-код и конвертация в иной код

- индикация/диагностика неисправности залипшей клавиши

Это если ТС заинтересуется расширением возможностей своего девайса. Например функция удержания/авто+/-

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

12 minutes ago, k155la3 said:

по "геттеру" если можно разъясните

Ну так функции-же: сеттер задает состояние, а геттер - получает его. Под ПК в софте обычно всегда реализацию скрывают, а доступ к свойствам выдают лишь через сеттеры-геттеры. В МК, понятно, зачастую объекты делают глобальными, чтобы уменьшить количество функций. Но никто не мешает геттер макросом сделать, чтобы красиво выглядело.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

30 минут назад, k155la3 сказал:

Это если ТС заинтересуется расширением возможностей своего девайса...

ИМХО, процессы опроса матричной клавиатуры и обработки состояний (выделение всяких "нажато"/"отжато"/"зажато"/и т.д.) нужно разносить. Потому что всяких обработок может набраться довольно много и на сам опрос кнопок это никакого влияния оказывать, по-хорошему, не должно (в плане кода). Например, я отправляю карту активных кнопок по определенному интерфейсу, соответственно я должен "защелкнуть" активные кнопки, обнаруженные между циклами передачи. Или, например, нужно отработать логику "зажатия" функциональной кнопки (яркость экрана). Все эти алгоритмы лишь должны взять текущее состояние матрицы, а не врастать корнями логических зависимостей в опрос этой матрицы.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Благодарю всех ответивших!

Не совсем ясно про опрос клавиатуры в основном цикле. Получается, просто бесконечно выполняем сканирование?

 

И еще вопрос - а на сколько жизнеспособен метод опроса с внешними прерываниями? Взять элемент 4И-НЕ, подключить к столбцам клавиатуры, выход на INT0. При изменении состояния любой кнопки генерируется прерывание. Отключаем INT0, запускаем счетчик, чтобы переждать дребезг, потом опрашиваем клавиатуру. Делают так на практике в реальных конструкциях?

50 минут назад, k155la3 сказал:

Для приложений с большим объемом ввода/использования клавиатуры имеет смысл ориентироваться на "событийный" режим ее работы

и соответствующий драйвер для этого функционала. Будут ли все ф-ии и возможности использоваться - другой вопрос.

Мне достаточно лишь различать короткое и длительное нажатие, не более того.

Изменено пользователем Evgeny

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

16 minutes ago, Arlleex said:

ИМХО, процессы опроса матричной клавиатуры и обработки состояний (выделение всяких "нажато"/"отжато"/"зажато"/и т.д.) нужно разносить. Потому что всяких обработок может набраться довольно много и на сам опрос кнопок это никакого влияния оказывать, по-хорошему, не должно (в плане кода). Например, я отправляю карту активных кнопок по определенному интерфейсу, соответственно я должен "защелкнуть" активные кнопки, обнаруженные между циклами передачи. Или, например, нужно отработать логику "зажатия" функциональной кнопки (яркость экрана). Все эти алгоритмы лишь должны взять текущее состояние матрицы, а не врастать корнями логических зависимостей в опрос этой матрицы.

не совсем это имелось ввиду. Процесс опроса и обработки состояний как раз в  одном блоке. Там-же - буфер строки ввода (ввод даты-времени пароля команд итп). На "выходе" в кольцевой буфер заносятся 16-разрядный код. Старший байт - битовая кодировка события. Младший байт - ASCII клавиши.

Этот драйвер вызывается раз в 30мс (не обязательно в векторе прерывания, не обязательно с такой точностью).

На "выходе" 2 "объекта" - (1) хвост буфера, оноже - последняя нажатая клавиша (2) сам буфер

В основном алгоритме следим за (1). Если это <ENTER> - переходим на "разбор" строки ввода.

ps (такой "огород" с буфером для обеспечения редактирования вводимой строки, встроенный в драйверный код)

В общем-то, это копия/клон работы с событиями клавиатуры в Windows, да и не только в ней наверное. Та-же PC клавиатура по своему интерфейсу работает аналогично.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

38 minutes ago, Evgeny said:

. . . . Мне достаточно лишь различать короткое и длительное нажатие, не более того.

Тут не "добровольный колхоз" из анекдота. Используйте то что посчитаете нужным-полезным. Просто обзор.

38 minutes ago, Evgeny said:

(1) Не совсем ясно про опрос клавиатуры в основном цикле. Получается, просто бесконечно выполняем сканирование?

(2) И еще вопрос - а на сколько жизнеспособен метод опроса с внешними прерываниями? . . . .

1. А почему бы и нет ? Если оно "есть не просит", те должно работать с экономией каждого микроампера.

Или надо жестоко экономить вычислительные/быстродействие процессора.

2. Жизнеспособен, особенно в устройствах с режимом энергосбережения. Выход из LPM по внешнему прерыванию.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Спасибо за разъяснения.

Тогда, пожалуй, переделаю под опрос по INT0, а по прерыванию таймера у меня будет выполняться только динамическая индикация.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 minutes ago, Evgeny said:

. . . Тогда, пожалуй, переделаю под опрос по INT0, а по прерыванию таймера у меня будет выполняться только динамическая индикация.

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Я планирую сразу же запрещать внешнее прерывание и запускать счетчик. А уж после того, как дребезг пройдет и опрос клавиш проведен, разрешать его вновь. Так тоже некорректно будет? 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

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

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...