Учебный курс. Опрос матричной клавиатуры. Пример использования автомата (State Machine)

Схема

подключение матричной клавиатуры к микроконтроллеру
  
   Резисторы R2 – R4, R8 – R11 предназначены для ограничения входного/выходного тока в случае неправильной настройки портов или одновременного нажатия нескольких кнопок. Выводы PD0(RXD), PD1(TXD) подключены к преобразователю UART-RS232, который на схеме не отображен. Обмен по USART`у используется для отладки программы.

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

   Строки клавиатуры подключены к выводам PD4, PD5, PD6, PD7. Они настроены на выход и в начальном состоянии на этих выводах напряжение логического нуля. Столбцы подключены к выводам PC0, PC1, PC2. Они настроены на вход, внутренние подтягивающие резисторы отключены и эти линии “придавлены к нулю питания” с помощью внешних резисторов номиналом в 10 КОм.
   Процедура сканирования клавиатуры выглядит следующим образом. Выставляем 1 на выводе PD4 и проверяем состояние выводов PC0, PC1, PC2 (то есть считываем содержимое регистра PINC). Если на каком-то из выводов установлена 1, значит, на клавиатуре в данный момент нажата кнопка, подключенная к первой строке. Сохраняем биты PD4, PD5, PD6, PD7 и PC0, PC1, PC2 в одной переменной – по этому коды мы будем определять номер нажатой кнопки. Если ни одна из кнопок не нажата, продолжаем процедуру сканирования.
   Сбрасываем 1 на выводе PD4 и устанавливаем 1 на выводе PD5. Снова проверяем состояние выводов PC0, PC1, PC2, и в случае нажатия кнопки сохраняем биты PD4, PD5, PD6, PD7 и PC0, PC1, PC2  в переменной.
   Повторяем описанную последовательность для двух оставшихся строк.

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

   Код, получаемый в процессе сканирования клавиатуры, часто требуется преобразовать в символьное значение номера/буквы кнопки (например, для передачи по USART`у). Для этого можно создать таблицу перекодировки - двумерный массив. В первом столбце таблицы будут храниться коды кнопок, а во втором соответствующие символьные значения. Методом последовательного перебора в таблице можно находить нужное значение. 

   Алгоритм опроса матричной клавиатуры можно реализовать в виде конечного автомата (State Machine) – функции, которая в зависимости от своего состояния (значения определенной переменной) и входного воздействия, выполняет разную работу. На рисунке ниже представлена диаграмма подобного автомат.
блок схема state machine
   Начальное состояние автомата - 0. В этом состоянии автомат находится, пока не будет нажата какая-нибудь кнопка. Когда зафиксировано нажатие на кнопку, запускается функция сканирования клавиатуры ScanKey(), запоминается код нажатой кнопки и автомат переходит в состояние 1.
   В состоянии 1 автомат проверяет, нажата ли в данный момент та же кнопка, что и в состоянии 0 или нет. Если коды кнопок не совпадают, автомат возвращается в состояние 0, если совпадают, запускается функция FindKey(), которая находит символьное значение номера кнопки и устанавливает флаги, сигнализирующие системе о нажатой кнопке. По завершению функции автомат переходит в состояние 2.
   Пока удерживается одна и та же кнопка, автомат находится в состоянии 2. Если произошли какие-то изменения, он переходит в состояние 3.
    Если изменения было случайным, автомат возвращается в состояние 2, а если нет, переходит в начальное состояние, чтобы снова запустить функцию сканирования клавиатуры.

Программная реализация автомата  

Представленную диаграмму конечного автомата легко преобразовать в программный код.

//хранит текущее состояние автомата
unsigned char keyState;

//прототипы функций используемых автоматом
unsigned char AnyKey(void);
unsigned char SameKey(void);
void ScanKey(void);
unsigned char FindKey(void);
void ClearKey(void);

void ScanKeyboard(void)
{
   switch (keyState){
     case 0:
       if (AnyKey()) {
         ScanKey();
         keyState = 1;
       }
       break;

     case 1:
       if (SameKey()) {
           FindKey();
           keyState = 2;
       }
       else keyState = 0;
       break;
    
     case 2:
        if (SameKey()){}
        else keyState = 3;
        break;
   
     case 3:
       if (SameKey()) {
         keyState = 2;
       }
       else {
         ClearKey();
         keyState = 0;
       }
       break;
    
     default:
        break;

   }
  
}

  Довольно наглядная запись. Теперь все, что нам остается, это написать/разобрать реализации недостающих функций. Чтобы код программы был максимально понятным, давайте обойдемся без макроопределений. Итак, по порядку.
 
unsigned char AnyKey(void)
{
  PORTD |= 0xf0; 
  return (PINC & 0x07);
}
   Устанавливаем на выводах  PD7 – PD4 единицы, возвращаем состояния выводов PC2 – PC0. Если какая-нибудь из кнопок клавиатуры в этот момент нажата, функция вернет значение отличное от нуля, то есть true.

//хранит код нажатой кнопки
unsigned char keyCode;

void ScanKey(void)
{
  unsigned char activeRow = (1<<4);
  while (activeRow) {
    PORTD = (PORTD & 0x0f)|activeRow;
    if (PINC & 0x07) {
      keyCode = (PINC & 0x07);
      keyCode |= (PORTD & 0xf0);
    }
    activeRow <<= 1;
  }
}
   Устанавливаем в 1 четвертый бит переменной activeRow (активизируем первую строку). Обнулив биты PD7 – PD4, записываем переменную в PORTD. Если какой-нибудь из трех младших разрядов регистра PINC установлен в единицу, значит в данный момент нажата кнопка, относящаяся к первой строке. Сохраняем биты PD7 – PD4 и PC2 - PC0 в переменную keyCode. Сдвигаем влево значение переменной activeRow на один разряд и повторяем цикл еще три раза.

unsigned char SameKey(void)
{
  PORTD = (PORTD & 0x0f) | ( keyCode & 0xf0);
  return ((PINC & keyCode) & 0x07);
}

  Функция проверяет, совпадает ли код нажатой в данный момент кнопки с кодом, полученным в предыдущем цикле опроса. Для этого на нужной строке устанавливается 1 – в PORTD записываются старшие 4 бита keyCode. Затем считывается регистр PINC, на него накладывается маска в виде переменной keyCode и выделяются 3 младших разряда. Полученное значение возвращается. Если коды кнопок совпадут, значение будет отлично от нуля, то есть true.

//хранит символьное значение нажатой кнопки
unsigned char keyValue;
//флаговая переменная - устанавливается, если кнопка удерживается
unsigned char keyDown;
//флаговая переменная -  устанавливается, когда нажата новая кнопка
unsigned char keyNew;

//таблица перекодировки
__flash unsigned char keyTable[][2] = {
{ 0x11, '1'},
{ 0x12, '2'},
{ 0x14, '3'},
{ 0x21, '4'},
{ 0x22, '5'},
{ 0x24, '6'},
{ 0x41, '7'},
{ 0x42, '8'},
{ 0x44, '9'},
{ 0x81, '*'},
{ 0x82, '0'},
{ 0x84, '#'}
};

unsigned char FindKey(void)
{
  unsigned char index;
  for (index = 0; index < 12; index++) {
    if (keyTable [index][0] == keyCode) {
      keyValue = keyTable [index][1];
      keyDown = 1;
      keyNew = 1;
      return 1;
    }
  }
  return 0;
}

  Эта функция ищет по таблице перекодировки номер кнопки, соответствующий коду, полученному на этапе сканирования клавиатуры. Если номер найден, он записывается в переменную keyValue и устанавливаются флаги keyDown и keyNew. Первый флаг сигнализирует о том, что кнопка удерживается нажатой, второй, что автомат зафиксировал нажатие новой кнопки.

void ClearKey(void)
{
  keyDown = 0;
}

   Эта функция сбрасывает флаговую переменную keyDown.

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

Полный текст программного модуля

keyboard.h
keyboard.c

Файлы

Тестовые проекты – микроконтроллер atmega8 опрашивает клавиатуру и посылает номер нажатой кнопки по USART`у в терминал.

IAR. Опрос матричной клавиатуры
WINAVR. Опрос клавиатуры
CodeVision. Опрос клавиатуры
Proteus – проект для отладки
Драйвер для матричной клавиатуры

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

При добавлении в комментарий Си кода, заключайте его между тегами [code] [/code]. Иначе он будет отображаться некорректно.


Защитный код
Обновить