AVR Урок 7. Кнопка



Урок 7

 

Кнопка

 

Сегодня мы расширим свой кругозор по изучению работы портов микроконтроллера и изучим второе назначение порта — работу на вход. И для изучения работы на вход мы применим обычную тактовую кнопку.

Как всегда, создадим проект в Atmel Studio, выберем Atmega8A, назовем проект Test04 и код также в main.c, как обычно, скопируем с проекта предыдущего урока.

В качестве подопытного порта давайте возьмём порт B. Можно с успехом использовать любой порт. И в качестве ножки возьмем нулевую ножку. Итак у нас ножка B0.

Также опять мы соберём проект, скопируем и переименуем файл протеуса, откроем его и в свойствах контроллера покажем путь к новому проекту. Запустим на выполнение и убедимся, что всё работает.

 Добавим кнопку в протеусе, для этого в поиске компонентов найдём Button

 

image00

 

Затем подключим нашу кнопку вот таким вот образом к ножке B0 контроллера

 

image01

 

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

Для этого мы, во-первых настроим порт B. Мы можем объявить все ножки порта B на вход, так как нам не важны настройки остальных ножке, ибо мы их не используем

 

DDRD = 0xFF;

DDRB = 0x00;

 

В случае, когда мы работали с портом D на выход, биты регистра PORTD отвечали за уровень на соответствующих ножках. А в случае, когда порт инициализирован на вход, как наш порт B, то биты регистра PORTB будут уже отвечать за подтягивание к соответствующим ножкам порта резисторов на шину питания. Если будет логическая единица, то регистр будет подтягиваться, а если логический ноль — то не будет. Поэтому мы в 0 бите регистра установим 1

 

PORTD = 0b00000001;

PORTB = 0b00000001;

 

Соберём код и запустим его в протеусе. Мы видим, что на ножке B0 у нас установилась логическая 1, а если мы нажмём кнопку, то увидим, что на ней будет логический 0, о чём свидетельствует синий цвет квадратиков на ножке и на кнопке.

В бесконечном цикле закомментируем весь код. В видеоверсии урока показано, как данную операцию можно выполнить одним движением (выделяем весь текст, который мы хотим закомментировать, затем нажимаем одновременно Shift и обычный слэш (не обратный))

 

// for(i=0;i<=7;i++)

// {

// PORTD = (1<<i);

// _delay_ms(500);

// }

}

 

В данном цикле мы и будем отслеживать состояние ножки PB0. Делается это с помощью определения состояния соответствующего бита в регистре PINB, который собственно за это и отвечает.

 

 

Чтобы нам следить за каким-либо действием или состоянием, нам необходимо будет обработать условие.

Условие в языке C добавляется с помощью команды if.

 

image02

 

И в качестве условия мы возьмём состояние ножки 0 порта B или состояние бита 0 регистра PINB.

Как же можно получить состояние одного бита, ведь в языке C в отличие от ассемблера нет битовых операций?

Можно пойти на хитрость и применить вот такую конструкцию PINB&0b00000001.

Данная конструкция нам и проверит нулевой бит. То есть если в регистре PINB также будет 1 в нулевом его бите, то независимо от состояния остальных битов в данном регистре мы получим ненулевой результат, что также является истиной. То есть если ни с чем не сравнивать в условии результат, то условие эквивалентно сравниванием с нулём, только наоборот. Для истинности результат должен быть ненулевым — (результат!=0).

Но нам с вами наоборот нежелательно, чтобы ножка была в высоком логическом состоянии, так как кнопка у нас подключена к общему проводу. Поэтому мы должны поставить отрицание и написать код следующим образом

 

while(1)

{

  if (!(PINB&0b00000001))

  {

  }

  else

  {

  }

 

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

 

DDRB = 0x00;

PORTD = 0b00000000;

PORTB = 0b00000001;

while(1)

{

  if (!(PINB&0b00000001))

  {

    PORTD = 0b00000001;

  }

  else

  {

    PORTD = 0b00000000;

  }

}

 

 

Теперь давайте пересоберём проект и пойдём в протеус смотреть, удалось ли нам что-то.

Чтобы у нас при сборке не было даже предупреждений, уберём объявление переменной i, так как она в коде не используется

 

int main(void)

{

    // unsigned char i;

    unsigned char butcount=0;

 

Запустим проект в протеусе и увидим, что при нажатии на кнопку у нас начинает светиться самый верхний светодиод

 

image03

 

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

И чтобы это как-то отследить и определить, что это было именно нажатие, а не дребезг, то мы будим отслеживать нажатие некоторое время, ну или некоторое количество тактов или циклов. Для этого в начале функции main() до бесконечного цикла мы добавим другую переменную (i нам ещё пригодится и мы её портить не будем). Назовём мы переменную butcount, так как имя переменной должно как-то само за себя говорить и тем самым достигается ещё большая читабельность кода

 

// unsigned char i;

unsigned char butcount=0;

 

И чтобы воспользоваться данной переменной, мы применим ещё одно условие. И у нас будет условие в условии. Это всё допустимо и очень широко используется. И в зависимости от этого условия мы данную переменную будем наращивать (инкрементировать). Условием будет у нас достижение данной переменной определённой величины. То есть попробуем сделать так, чтобы значение переменной не достигало 5

 

if (!(PINB&0b00000001))

{

  if(butcount < 5)

  {

    butcount++;

  }

 

А когда значение данной переменной достигнет значения 5, то мы уже в данный цикл не попадём, а попадём мы в тело оператора else, который мы сейчас и добавим и в его теле напишем следующий код

 

  butcount++;

}

else

{

  PORTD = 0b00000001;

}

 

То есть мы как раз после достижения пятёрки и будем обрабатывать нажатие кнопки и включать на нулевой ножке порта D высокое состояние.

По идее, здесь мы должны обнулить нашу переменную, но мы это будем делать также постепенно, используя тело оператора else, только другого — того, который у нас был и тело которого выполняется при низком уровне на ножке, к которой подключена кнопка. Вот таким будет его тело

 

else

{

  if (butcount > 0)

  {

    butcount—;

  }

  else

  {

    PORTD = 0b00000000;

  }

}

 

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

 

Давайте теперь соберём проект и проверим его работу сначала в протеусе, а затем и на практике. Выглядит это приблизительно так. Интереснее конечно это смотреть в видеоуроке

 

image04

 

 

Предыдущий урок Программирование МК AVR Следующий урок

 

Исходный код

 

 

Приобрести программатор USBASP USBISP с адаптером можно здесь USBASP USBISP 3.3 с адаптером

 

Смотреть ВИДЕОУРОК на RuTube (нажмите на картинку)

AVR Кнопка

 

Смотреть ВИДЕОУРОК на YouTube (нажмите на картинку)

AVR Кнопка

 

35 комментариев на “AVR Урок 7. Кнопка
  1. Ant:

    Спасибо, в этом уроке, как и в остальных, очень доходчиво написано все.

  2. alex:

    А почемц butcount это unsigned char? Может unsigned int?

    • Andrew:

      потому  что char  8 бит  об'явление переменной , а int  16  бит

      для сокращения можно об'явить безнаковое 8 бит uint8_t

      для 16 бит uint16_t

  3. sibiryak69:

    Подскажите пожалуйста что значит это выражение (!(PINB&0b00000001))

  4. Александр:

    Выражение для проверки на ноль (0) 1-го бита (канал PB0) в регистре порта PORTB.

  5. sibiryak69:

    Я имел в виду восклицательный знак перед скобкой?

    • Логическая операцию инверсии "!" (логическое НЕ). Логическая операция "!" переворачивает логическое значение с правды (True) на ложь (False), и наоборот. Например, запись PINB&0b00000001 проверяет вывод на 1, если же поставить операнд ! ,запись вида (!(PINB&0b00000001)) будет осуществлять проверку на 0.

      • Дмитрий:

        Логическое НЕ ведь обозначается «~»? Или это одно и то же?
        Спасибо.

        • Даже не видел такого обозначения.
          Это в каком языке так обозначается?

          • Дмитрий:

            А четвертая операция — операция «НЕ», обозначаемая знаком тильда — «~», проделывается над одним байтом. В результате данной операции все биты меняются обратный. То есть ноль становится единицей, а единица — нулём. Данную операцию ещё называют операцией инвертирования.

            Вот такой вот получим результат

            image05
            Урок №5

          • Дмитрий:

            Ещё в следующем уроке: инвертируем байты чисел индикатора.
            Пожалуйста, уточните.
            Спасибо

          • Геннадий:

            Вы же сами написали : логическое НЕ обозачается «~» . А тут «!». Где же тогда истина?

          • Это не логическое НЕ, это инверсия. Также существует ещё побитовое НЕ. Смотрите мои уроки по языку СИ, там намного понятнее, поэтому предусматривается, что если программируем МК, то язык знаем.

  6. Александр:

    Спасибо за уроки! за один вече научился программировать, хотя раньше никогда не занимался этим))
    такой вопрос: как сделать, чтобы диод загорался при одновременном нажатии 2х кнопок

  7. nikolaevvv:

    Прекрасные уроки, а главное все доходчиво! Хотелось бы увидеть от Вас урок по кнопке с фиксацией с правильным алгоритмом антидребезга.

  8. REQW:

    Доброго дня! Спасибо за интересные уроки.
    Вопрос такой: объясните почему выражение for { (!(PINB&0b00000001)) … } нельзя заменить выражением for { (PINB != 0b00000001) … }?

    • Спасибо за столь высокую оценку ресурса!
      Потому что в левом выражении мы проверяем только один бит, а в Вашем правом — все биты байта, получается, что биты от 7 до 1 должны быть обязательно не равны нулю.

  9. jurchik323:

    Здравствуйте! В ассемблере на PICах я изучал алгоритм кнопки такой: при первом нажатии кнопки светодиод включался, при повторном нажатии выключался. Как этот алгоритм осуществить на Си и для Atmega. Пока не врубаюсь. Если можете помогите пожалуйста.

  10. VVV:

    долго всматривался и вглядывался… но так и не уловил.. поясните по возможности.
    Выполняя вот эту
    if (!(PINB&0b00000001))
    операцию мы работаем только с одним битом порта В, а конкретно с В0!
    тогда почему далее вот тут
    {PORTD = 0b00000001;}
    else
    {PORTD = 0b00000000;}
    у нас запись и обнуление идёт полностью на весь порт D? (ведь у нас какието ноги порта D могут быть заняты чем то иным)
    как записать единицу отдельно в PD0 понятно. (например PORTD=_BV(DO);)
    а вот как обнулить отдельно DO не трогая весь порт я чтото не пойму.
    Спасибо.

  11. VVV:

    Ошибка и продолжение:
    Забыл вертикальный слеш… конечно же чтобы записать отдельно в PD0 надо
    PORTD|=_BV(D0);
    А как записать отдель 0(нуль) в этот т же бит не трогая остальные?
    PORTD&=~_BV(PD0);
    вот так?
    Тогда ваша кобинация получится вот такой:
    if (!(PINB&0b00000001))
    PORTD|=_BV(D0);
    else
    PORTD&=~_BV(PD0);

    Я правильно понимаю?
    Спасибо.

  12. Артур:

    Спасибо за ваши уроки! Только благодаря вам стал немного понимать программирование.
    Написал вот такую программу, правда в Mikro C. Микроконтроллер PIC12F629. При включении питания ничего не светится, нажимаем кнопку 1 раз, включается первый светодиод, нажимаем еще раз кнопку выкл. 1 светодиод вкл. 2 светодиод и так далее до пятого, после него нажатием кнопки выключены все.
    У меня стоит задача, чтобы в каждом кейсе был бесконечный цикл, переключаемый кнопкой (он изображен в программе), но добиться этого я не могу. Прошу вашей помощи, если это вас не затруднит.

    char AUX=0, POS=0;
    void main() {
    CMCON = 0x07;
    GPIO = 0x00;
    INTCON = 0x88;
    TRISIO = 0x08;

    while (1)
    {
    if (!GPIO.GP3==1 && AUX==0)
    {
    switch (POS){
    case 0:
    GPIO.GP0 = 1;
    //while (2)
    //{
    //GPIO.GP0 = 1;
    //delay_ms(150);
    //GPIO.GP0 = 0;
    //GPIO.GP1 = 1;
    //delay_ms(150);
    //GPIO.GP1 = 0;
    //}
    //return;
    POS=1;
    break;
    case 1:
    GPIO.GP0 = 0;
    GPIO.GP1 = 1;
    POS=2;
    break;
    case 2:
    GPIO.GP1 = 0;
    GPIO.GP2 = 1;
    POS=3;
    break;
    case 3:
    GPIO.GP2 = 0;
    GPIO.GP4 = 1;
    POS=4;
    break;
    case 4:
    GPIO.GP4 = 0;
    GPIO.GP5 = 1;
    POS=5;
    break;
    case 5:
    GPIO.GP5 = 0;
    POS=0;
    break;
    }
    AUX=1;
    delay_ms(100);
    }
    if (!GPIO.GP3==0 && AUX==1)
    {
    AUX=0;
    delay_ms(100);
    }
    //delay_ms(50);
    }
    }

  13. Артур:

    Еще раз спасибо за уроки. Проблему решил.

  14. Артур:

    Теперь новая задача: чтобы после отключения питания и включения вновь автоматически запускался кейс, который работал при выключении.
    Я тихо схожу с ума.
    Можете не обращать внимание на мои письмена.

    • Роман:

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

  15. Олег:

    Здравствуйте.
    Скажите пожалуйста, как реализовать возможность регулировки скорости переключения светодиодов например в уроке номер 6 при помощи двух кнопок + и -. В инете ничего не могу найти. Помогите кто нибудь.

  16. Ivan:

    Встречал подобную защиту от дребезга,

    while (PIND==0b00000000){}
    _delay_ms(40);
    ……..
    while (PIND==0b00000001){}
    _delay_ms(40);

    насколько она актуальна?

    • Wino Veritas:

      Самый актуальный способ подавить дребезг контактов — это триггер Шмитта (SN74HC14N) или специальная логическая ИС Устройство удаления дребезга контактов (MC14490). Да, конечно, требуется дополнительная обвязка, но зато 99.9% надёжно.

    • Иван:

      Я именно по таймеру и делал всегда, на практике не подводило)

  17. Григорий:

    кнопку нужно подключать между резистором, который подключен к +5В и портом контроллера, так при считывании состояния порта контроллер точно будет идентифицировать ее как 1 при отжатой кнопке и как 0 при нажатой.

    • Wino Veritas:

      Однозначно согласен. Еще хорошо себя зарекомендовал керамический конденсатор емкостью 0.22 мкФ параллельно кнопке.

  18. Роман:

    Проверить условие нажатия двух кнопок.

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*