Матричная клавиатура

Тема в разделе "Микроконтроллеры AVR", создана пользователем Aleksander1997, 11 мар 2018.

  1. Нашел код без библиотек, для моей клавиатуры как раз. Смысл такой: по нажатию клавиши в монитор порта выводится символ этой клавиши. У меня arduino uno на мк atmega328p. Код работает, но мешает развитию delay.
    Код (C++):
    /*** Подключение матричной клавиатуры к микроконтроллеру AVR ***/
    #include <avr/io.h> // библиотека в которой находятся определения констант, имен регистров avr

    const char value[6] = {'1', '2', '3', '4', '5', '6'};

    void setup() {
      DDRB |= (1 << PB5)|(1 << PB4)|(1 << PB3)|(1 << PB2)|(1 << PB1)|(1 << PB0); // инициализируем порты на выход(столбцы)
      DDRC &= ~(1 << PC2); // порт на вход(строка)
      PORTC |= 1 << PC2; // подключить внутренние подтягивающие pull-up резисторы

      Serial.begin(9600); // открываем Serial порт
    }

    void scan_key(){ // создаем функцию для чтения кнопок
      for (byte i = 0; i <= 5; i++) {  // цикл, передающий 0 по всем столбцам
        PORTB &= ~(1 << i);  // если i меньше 6 , то отправляем 0 на ножку
        if (!(PINC&0x04)) {  // если порт входа равен 0, то..
            Serial.println(value[i]);
            delay(175);
        }
            PORTB |= 1 << i;  // подаём обратно высокий уровень
      }
    }

    void loop() {
      scan_key(); // используем функцию опроса матричной клавиатуры
    }
     
  2. Да строка повешена на порт PC2, столбцы занимают 6 ножек порта B
     
  3. Узнал что можно опрашивать клавиатуру через прерывание по таймеру - Я так и решил действовать. На сайте http://www.instructables.com/id/Arduino-Timer-Interrupts/ вполне понятно объяснено о работе таймеров, конкетно о прерывание по таймеру. Вот код взятый оттуда blink на таймере 1 каждую секунду
    Код (C++):
    //#define F_CPU 16000000UL
    #include <avr/io.h> // библиотека в которой находятся определения констант, имен регистров avr
    #include <avr/interrupt.h>

    boolean toggle1 =0;

    void setup() {
      DDRB |= 1 << PB5;
      TCCR1A = 0;
      TCCR1B = 0;
      TCNT1 = 0; // начальное значение таймера
      OCR1A = 15624; // 16000000 / (1024 * 1) - 1 = 15624
      TCCR1B |= (1<<CS10)|(1<<CS12)|(1<<WGM12); // делитель f/1024, устанавливаем режим СТС (сброс по совпадению)
      TIMSK1 = (1 << OCIE1A); //устанавливаем бит разрешения прерывания по совпадению с OCR1A
      sei(); // Выставляем бит общего разрешения прерываний
    }

    ISR(TIMER1_COMPA_vect){ // timer 1
      if(toggle1){
        PORTB |= 1 << PB5;
        toggle1 = 0;
      }
      else {
        PORTB &= ~(1 << PB5);
        toggle1 = 1;
      }
    }

    void loop() {

    }
     
  4. На основе предыдущего написал код, который задейтвует 5 клавиш клавиатуры, шестую заменил светодиод. Смысл в вызове функции обработчика прерываний каждые 50 мс для устранения дребезга, но код не фурычит. Алгоритм кода - по нажатию любой клавиши должен загореться диод. Подскажите в чем ошибся?
    Код (C++):
    //#define F_CPU 16000000UL
    #include <avr/io.h> // библиотека в которой находятся определения констант, имен регистров avr
    #include <avr/interrupt.h>

    volatile byte i;

    void setup() {
      DDRB |= (1 << PB5)|(1 << PB4)|(1 << PB3)|(1 << PB2)|(1 << PB1)|(1 << PB0); // инициализируем порты на выход(столбцы)
      DDRC &= ~(1 << PC2); // порт на вход(строка)
      PORTC |= 1 << PC2; // подключить внутренние подтягивающие pull-up резисторы
     
      TCCR1A = 0;
      TCCR1B = 0;
      TCNT1 = 0; // начальное значение таймера
      OCR1A = 780; // 16000000 / (1024 * 20) - 1 = 780 -> частота опроса клавиатуры 50 мс
      TCCR1B |= (1<<CS10)|(1<<CS12)|(1<<WGM12); // делитель f/1024, устанавливаем режим СТС (сброс по совпадению)
      TIMSK1 = (1 << OCIE1A); //устанавливаем бит разрешения прерывания по совпадению с OCR1A
      sei(); // Выставляем бит общего разрешения прерываний
    }

    ISR(TIMER1_COMPA_vect){ // timer 1
      for (i = 0; i <= 4; i++) {  // цикл, передающий 0 по всем столбцам
        PORTB &= ~(1 << i);  // если i меньше 6 , то отправляем 0 на ножку
        if (!(PINC&0x04)) {  // если порт входа равен 0, то..
            PORTB |= 1 << PB5;
        }
            PORTB |= 1 << i;  // подаём обратно высокий уровень
      }
    }

    void loop() {

    }
     
  5. Извините но правильно ли будет делать проверку нажатия клавиши в прерывании и уже в функции loop зажигать светодиод?
     
  6. mcureenab

    mcureenab Гуру

    Тут должен диод включаться?

    Код (C++):
     PORTB |= 1 << PB5;
    А где он выключается?

    И пин PB5 настроен быть входом

    Код (C++):
      DDRB |= (1 << PB5)|(1 << PB4)|(1 << PB3)|(1 << PB2)|(1 << PB1)|(1 << PB0); //инициализируемпорты на выход(столбцы)
     
     
  7. mcureenab

    mcureenab Гуру

    И так и эдак можно делать в зависимости от ситуации.

    Но лучше всё что можно делать в функции loop лучше делать там, а не в прерываниях.

    Тут прерывания для опроса клавиатуры не нужны.
     
    Последнее редактирование: 11 мар 2018
  8. Еще раз добрый вечер:). Освободился от дневной рутины и решил заняться своим вопросом. Клавиатура мне понадобилась для самодельной инфракрасной паяльной станции. Раньше эти функции повесил на обыкновенные тактовые кнопки, отрабатывалось короткое и долгое нажатие. Но несомненный плюс клавиатуры в удобстве подключения(готовый шлейф) и она более компактна. У ардуино почти все рабочие ножки заняты: дисплей, термопары и реле, клавиатура занимает 7 ног. Пока решил поработать в связке atmel studio 6 и proteus

    Код (C++):
    #define F_CPU 16000000UL
    #include <avr/io.h> // библиотека в которой находятся определения констант, имен регистров avr
    #include <avr/interrupt.h>

    //volatile uint8_t i;
    volatile uint8_t val;

    ISR(TIMER1_COMPA_vect){ // прерывание timer 1 каждые 500 мс
        for (uint8_t i = 0; i <= 5; i++) {  // цикл, передающий 0 по всем столбцам
            PORTB &= ~(1 << i);  // если i меньше 6 , то отправляем 0 на ножку
            if (!(PINC&0x04)) {  // если порт входа равен 0, то..
                //PORTD |= 1 << PD7;
                val = !val;
            }
            PORTB |= 1 << i;  // подаём обратно высокий уровень
        }
    }

    int main(void) {
        DDRB |= (1 << PB5)|(1 << PB4)|(1 << PB3)|(1 << PB2)|(1 << PB1)|(1 << PB0); // инициализируем порты на выход(столбцы)
        DDRC &= ~(1 << PC2); // порт на вход(строка)
        PORTC |= 1 << PC2; // подключить внутренние подтягивающие pull-up резисторы

        DDRD |= (1 << PD5); // на выход(led)
        TCCR1A = 0;
        TCCR1B = 0;
        TCNT1 = 0; // начальное значение таймера
        OCR1A = 7811; // 16000000 / (1024 * 2) - 1 = 7811 -> частота опроса клавиатуры 500 мс
        TCCR1B |= (1<<CS10)|(1<<CS12)|(1<<WGM12); // делитель f/1024, устанавливаем режим СТС (сброс по совпадению)
        TIMSK1 = (1 << OCIE1A); //устанавливаем бит разрешения прерывания по совпадению с OCR1A
        sei(); // Выставляем бит общего разрешения прерываний
        while(1) {
            if(val){
                PORTD |= 1 << PD5;
            }
            else {
                PORTD &= ~(1 << PD5);
            }
        };
    }
    Проделал симуляцию в программе proteus 8. Прерывание отрабатывает как надо: по фиксированному нажатию любой кнопки светодиод мигает с частотой 2 герца(500 ms). Буду усложнять
    upload_2018-3-12_3-20-46.png
     
    Последнее редактирование: 12 мар 2018
  9. Подумаю над этим
     
  10. План действий
    По прерыванию мы запускаем цикл
    В цикле:
    1. Сбрасываем соответствующую линию порта Х(столбцы) в "0"
    2. Читаем состояние порта Y(строка)
    3. Записываем (и в случае необходимости сдвигаем) информацию
    в переменную. Переменная - часть массива.
    4. Возвращаем соответствующую линию порта Х в "1"
    5. Переходим к следующему шагу цикла.
    По окончании цикла состояние всех клавиш нужно
    сравнивать с предыдущим (антидребезг), можно проверить длительность
    нажатия клавиши.
     
  11. Эх не могу понять где ошибся...
    Откуда взял идею, прикладываю ссылку http://asis-kbr.ru/forum/viewtopic.php?f=13&t=173
    Нерабочий вариант:
    Код (C++):

    // светодиоды на порту С, кнопки на на порту B
    #define F_CPU 16000000UL
    #include <avr/io.h> // библиотека в которой находятся определения констант, имен регистров avr
    #include <avr/interrupt.h>

    #define BTN_SHRT_UP      (1<<0)        /*бит короткого нажатия кнопки up*/
    #define BTN_SHRT_DN      (1<<1)        /*бит короткого нажатия кнопки dn*/
    #define BTN_SHRT_LEFT    (1<<2)        /*бит короткого нажатия кнопки left*/
    #define BTN_SHRT_RIGHT   (1<<3)        /*бит короткого нажатия кнопки right*/
    #define BTN_SHRT_OK      (1<<4)        /*бит короткого нажатия кнопки up*/
    #define BTN_SHRT_CANSEL  (1<<5)        /*бит короткого нажатия кнопки dn*/

    volatile uint8_t BtnFlags;

    //функция чтения данных о нажатии кнопок
    char BtnGet (void)
    {
        cli();
        char temp = BtnFlags;
        BtnFlags = 0;
        sei ();
        return temp;
    }

    void Scan(void) {

    static unsigned char lockBit;    //защелка (защита от дребезга)
    static unsigned char lockCount;  //счетчик защелки (защита от дребезга)
    static unsigned char longCount;  //счетчик длинного нажатия
    static unsigned char lastState;  //последнее состояние кнопок перед отпусканием
    for (uint8_t i = 0; i <= 5; i++) {  // цикл, передающий 0 по всем столбцам
        PORTB &= ~(1 << i);  // если i меньше 6 , то отправляем 0 на ножку
        char mask = 0;
        if (!(PIND&(1<<7))) {  // если порт входа равен 0, то..
            mask = (1 << i); //
        }
        PORTB |= 1 << i;  // подаём обратно высокий уровень

        if (mask) {                           //опрос состояния кнопки
           if (lockCount < 5) {               //клавиша нажата
              lockCount++;
              return;                         //защелка еще не досчитала - возврат
           }
           lastState = mask;
           lockBit = 1;                        //нажатие зафиксировано
           //if (longCount >= 50)
            //  return; //возврат, т.к. счетчик длинного нажатия досчитал до максимума еще раньше
           //if (++longCount >= 50)
           //  BtnFlags |= (lastState<<4);        //счетчик досчитал до максимума - устанавливаем биты длинного нажатия
        }
        else {                                   //клавиша отжата
            if (lockCount){
                lockCount--;
                return;                         //защелка еще не обнулилась - возврат
            }
            if (!lockBit)                        //СТАТИЧЕСКИЙ ВОЗВРАТ
            return;
            lockBit =0;                            //отжатие зафиксировано
            if (longCount < 100)
            BtnFlags |= lastState;            //установка бита короткого нажатия
            longCount = 0;                    //сброс счетчика длительности нажатия
        }
    }
    }

    ISR(TIMER1_COMPA_vect){ // timer 1
      Scan();
    }

    int main(void) {
        DDRB |= (1 << PB5)|(1 << PB4)|(1 << PB3)|(1 << PB2)|(1 << PB1)|(1 << PB0); // инициализируем порты на выход(столбцы)
        DDRD &= ~(1 << PD7); // порт на вход(строка)
        PORTD |= 1 << PD7; // подключить внутренние подтягивающие pull-up резисторы
        DDRC |= 0x3F; // на выход(led)

        TCCR1A = 0;
        TCCR1B = 0;
        TCNT1 = 0; // начальное значение таймера
        TCCR1B |= (1<<CS10)|(1<<CS12)|(1<<WGM12); // делитель f/1024, устанавливаем режим СТС (сброс по совпадению)
        OCR1A = 155; // 16000000 / (1024 * 100) - 1 = 155 -> частота опроса клавиатуры 10 мс
        TIMSK1 = (1 << OCIE1A); //устанавливаем бит разрешения прерывания по совпадению с OCR1A
        sei(); // Выставляем бит общего разрешения прерываний
        while(1) {
           char BtnMask = BtnGet();
           if (BtnMask == BTN_SHRT_UP)     PORTC |= 1 << PC0;  //{.....}
           if (BtnMask == BTN_SHRT_DN)     PORTC |= 1 << PC1;  //{.....}
           if (BtnMask == BTN_SHRT_RIGHT)  PORTC |= 1 << PC2;  //{.....}//обработка короткого нажатия ВПРАВО
           if (BtnMask == BTN_SHRT_LEFT)   PORTC |= 1 << PC3;  //{.....}//обработка короткого нажатия ВЛЕВО
           if (BtnMask == BTN_SHRT_OK)     PORTC |= 1 << PC4;  //{.....}
           if (BtnMask == BTN_SHRT_CANSEL) PORTC |= 1 << PC5;  //{.....}
        };
    }
    ТЗ: по нажатию одной любой из кнопок должен загореться соответствующий светодиод.
    upload_2018-3-12_23-22-48.png
     
    Последнее редактирование: 13 мар 2018
  12. При запуске симуляции и нажатии по очереди на кнопки(половина светодиодов не горит). Буду очень благодарен если укажите на ошибку
    upload_2018-3-12_23-50-41.png
     
  13. AlexU

    AlexU Гуру

    Что-то Вы слушком мудрите с этой клавиатурой...
    Диоды на схеме встроенные в клавиатуру или Вы подключаете внешние?
    Эту клавиатуру матричной можно назвать только с натяжкой. Если Вы правильно нарисовали схему, то это просто шесть кнопок с общим выводом. Этот общий вывод подключаете либо к плюсу, либо к минусу (если диоды не встроенные), а остальные шесть выводов к порту, который сконфигурирован как входной (только с подтяжкой надо вопрос решить -- зависит от ответа на вопрос выше). И единственно что остаётся -- это опрашивать этот входной порт.
     
  14. Отвечу по порядку
    Диоды встроенные в клавиатуру. Алгоритм опроса клавиатуры действительно не сложный, но мне потребовалось его делать в прерывании. По сути должно работать, но я где то допустил просчет
     
  15. AlexU

    AlexU Гуру

    Ну раз так, то общий вывод кнопок на "плюс" и все кнопки подтягиваем внешними резисторами (примерно 10к) к "земле". По другому входной порт (PB) будет ловить помехи.
    В прерывании читаем регистр PINB, какие биты в '1' -- та кнопка нажата.
     
  16. Спасибо, но у меня в симуляторе все заработало, условие неверное было, исправил. Попробую теперь задейсвовать долгие нажатия( ими гасить диоды)
    Код (C++):
    // По прерыванию мы запускаем цикл

    #define F_CPU 16000000UL
    #include <avr/io.h>
    #include <avr/interrupt.h>

    #define BTN_SHRT_UP      (1<<0)  /*бит короткого нажатия кнопки up*/
    #define BTN_SHRT_DN      (1<<1)  /*бит короткого нажатия кнопки dn*/
    #define BTN_SHRT_LEFT    (1<<2)  /*бит короткого нажатия кнопки left*/
    #define BTN_SHRT_RIGHT   (1<<3)  /*бит короткого нажатия кнопки right*/
    #define BTN_SHRT_OK      (1<<4)  /*бит короткого нажатия кнопки up*/
    #define BTN_SHRT_CANSEL  (1<<5)  /*бит короткого нажатия кнопки dn*/

    #define LOCK_TIME  50    /*время обработки дребезга в мс (10-100)*/
    #define LONG_TIME  1000  /*время фиксации длинного нажатия в мс (1000 - 2500)*/

    volatile uint8_t BtnFlags;

    //функция чтения данных о нажатии кнопок
    char BtnGet (void)
    {
        cli();
        char temp = BtnFlags;
        BtnFlags = 0;
        sei ();
        return temp;
    }

    void Scan(void) {
     
    static unsigned char lockBit;    //защелка (защита от дребезга)
    static unsigned char lockCount;  //счетчик защелки (защита от дребезга)
    static unsigned char longCount;  //счетчик длинного нажатия
    static unsigned char lastState;  //последнее состояние кнопок перед отпусканием
    char mask = 0;
    for (uint8_t i = 0; i <= 5; i++) {  // цикл, передающий 0 по всем столбцам
        PORTB &= ~(1 << i);  // если i меньше 6 , то отправляем 0 на ножку
        if (!(PIND&(1<<7))) {  // если порт входа равен 0, то..
            mask = (1 << i); //
        }
        PORTB |= 1 << i;  // подаём обратно высокий уровень
        }
        if (mask) {                           //опрос состояния кнопки
           if (lockCount < (LOCK_TIME/10)) {  //клавиша нажата
              lockCount++;
              return;                         //защелка еще не досчитала - возврат
           }
           lastState = mask;
           lockBit = 1;                        //нажатие зафиксировано
           //if (longCount >= (LONG_TIME/10))
            //  return; //возврат, т.к. счетчик длинного нажатия досчитал до максимума еще раньше
           //if (++longCount >= (LONG_TIME/10))
           //  BtnFlags |= (lastState<<4);        //счетчик досчитал до максимума - устанавливаем биты длинного нажатия
        }
        else {                          //клавиша отжата
            if (lockCount){
                lockCount--;
                return;                 //защелка еще не обнулилась - возврат
            }
            if (!lockBit)               //СТАТИЧЕСКИЙ ВОЗВРАТ
            return;
            lockBit =0;                 //отжатие зафиксировано
            if (longCount < (LONG_TIME/10))
            BtnFlags |= lastState;      //установка бита короткого нажатия
            longCount = 0;              //сброс счетчика длительности нажатия
        }
    }

    ISR(TIMER1_COMPA_vect){ // timer 1
      Scan();
    }

    int main(void) {
        DDRB |= (1 << PB5)|(1 << PB4)|(1 << PB3)|(1 << PB2)|(1 << PB1)|(1 << PB0); // инициализируем порты на выход(столбцы)
        DDRD &= ~(1 << PD7);  // порт на вход(строка)
        PORTD |= 1 << PD7;    // подключить внутренние подтягивающие pull-up резисторы
        DDRC |= 0x3F;         // на выход(led)
     
        TCCR1A = 0;
        TCCR1B = 0;
        TCNT1 = 0; // начальное значение таймера
        OCR1A = 155; // 16000000 / (1024 * 100) - 1 = 155 -> частота опроса клавиатуры 10 мс
        TCCR1B |= (1<<CS10)|(1<<CS12)|(1<<WGM12); // делитель f/1024, устанавливаем режим СТС (сброс по совпадению)
        TIMSK1 = (1 << OCIE1A); //устанавливаем бит разрешения прерывания по совпадению с OCR1A
        //sei(); // Выставляем бит общего разрешения прерываний
        while(1) {    
           char BtnMask = BtnGet();
           if (BtnMask == BTN_SHRT_UP)     PORTC |= 1 << PC0;  //{.....}
           if (BtnMask == BTN_SHRT_DN)     PORTC |= 1 << PC1;  //{.....}
           if (BtnMask == BTN_SHRT_LEFT)  PORTC |= 1 << PC2;  //{.....}//обработка короткого нажатия ВПРАВО
           if (BtnMask == BTN_SHRT_RIGHT)   PORTC |= 1 << PC3;  //{.....}//обработка короткого нажатия ВЛЕВО
           if (BtnMask == BTN_SHRT_OK)     PORTC |= 1 << PC4;  //{.....}
           if (BtnMask == BTN_SHRT_CANSEL) PORTC |= 1 << PC5;  //{.....}
        };
    }
    upload_2018-3-13_13-53-28.png
     
  17. mcureenab

    mcureenab Гуру

    переменные не мешало бы явно инициализировать.

    Код (C++):
    static unsigned char lockBit=0;    //защелка (защита от дребезга)
    static unsigned char lockCount=0;  //счетчик защелки (защита от дребезга)
    static unsigned char longCount=0;  //счетчик длинного нажатия
    static unsigned char lastState=0;  //последнее состояние кнопок перед отпусканием
     
     
  18. AlexU

    AlexU Гуру

    Хорошо что заработало, но код можно сильно упростить....
     
  19. parovoZZ

    parovoZZ Гуру

    Да уж, да уж))
    ТС, если не секрет, сколько занимает программа в памяти и переменные в ОЗУ?