Урок 7
Кнопка
Сегодня мы расширим свой кругозор по изучению работы портов микроконтроллера и изучим второе назначение порта — работу на вход. И для изучения работы на вход мы применим обычную тактовую кнопку.
Как всегда, создадим проект в Atmel Studio, выберем Atmega8A, назовем проект Test04 и код также в main.c, как обычно, скопируем с проекта предыдущего урока.
В качестве подопытного порта давайте возьмём порт B. Можно с успехом использовать любой порт. И в качестве ножки возьмем нулевую ножку. Итак у нас ножка B0.
Также опять мы соберём проект, скопируем и переименуем файл протеуса, откроем его и в свойствах контроллера покажем путь к новому проекту. Запустим на выполнение и убедимся, что всё работает.
Добавим кнопку в протеусе, для этого в поиске компонентов найдём Button
Затем подключим нашу кнопку вот таким вот образом к ножке B0 контроллера
Судя по данной схеме, когда кнопка будет в нажатом состоянии, то Ножка 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.
И в качестве условия мы возьмём состояние ножки 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;
Запустим проект в протеусе и увидим, что при нажатии на кнопку у нас начинает светиться самый верхний светодиод
Казалось бы, что мы своей цели уже добились. Но чтобы сделать наш код более ответственным и совершенным, мы просто обязаны провести борьбу с дребезгом контактов, так как такое явление может иметь место, это только в протеусе всё идеально, на практике такое бывает не всегда.
И чтобы это как-то отследить и определить, что это было именно нажатие, а не дребезг, то мы будим отслеживать нажатие некоторое время, ну или некоторое количество тактов или циклов. Для этого в начале функции 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;
}
}
Данный код чем то похож на предыдущий, только здесь у нас идёт, наоборот декрементирование переменной, и как только её значение опять достигнет нуля, то мы и попадём в обработку отжатия кнопки, тем самым полностью избавимся от дребезга. И чем старее и некачественнее будет наша тактовая кнопка, тем большее значение переменной в условии мы будем применять.
Давайте теперь соберём проект и проверим его работу сначала в протеусе, а затем и на практике. Выглядит это приблизительно так. Интереснее конечно это смотреть в видеоуроке
Предыдущий урок Программирование МК AVR Следующий урок
Приобрести программатор USBASP USBISP с адаптером можно здесь USBASP USBISP 3.3 с адаптером
Смотреть ВИДЕОУРОК на RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК на YouTube (нажмите на картинку)
Спасибо, в этом уроке, как и в остальных, очень доходчиво написано все.
А почемц butcount это unsigned char? Может unsigned int?
потому что char 8 бит об'явление переменной , а int 16 бит
для сокращения можно об'явить безнаковое 8 бит uint8_t
для 16 бит uint16_t
Подскажите пожалуйста что значит это выражение (!(PINB&0b00000001))
Выражение для проверки на ноль (0) 1-го бита (канал PB0) в регистре порта PORTB.
Я имел в виду восклицательный знак перед скобкой?
Логическая операцию инверсии "!" (логическое НЕ). Логическая операция "!" переворачивает логическое значение с правды (True) на ложь (False), и наоборот. Например, запись PINB&0b00000001 проверяет вывод на 1, если же поставить операнд ! ,запись вида (!(PINB&0b00000001)) будет осуществлять проверку на 0.
Логическое НЕ ведь обозначается «~»? Или это одно и то же?
Спасибо.
Даже не видел такого обозначения.
Это в каком языке так обозначается?
А четвертая операция — операция «НЕ», обозначаемая знаком тильда — «~», проделывается над одним байтом. В результате данной операции все биты меняются обратный. То есть ноль становится единицей, а единица — нулём. Данную операцию ещё называют операцией инвертирования.
Вот такой вот получим результат
image05
Урок №5
Ещё в следующем уроке: инвертируем байты чисел индикатора.
Пожалуйста, уточните.
Спасибо
Вы же сами написали : логическое НЕ обозачается «~» . А тут «!». Где же тогда истина?
Это не логическое НЕ, это инверсия. Также существует ещё побитовое НЕ. Смотрите мои уроки по языку СИ, там намного понятнее, поэтому предусматривается, что если программируем МК, то язык знаем.
Спасибо за уроки! за один вече научился программировать, хотя раньше никогда не занимался этим))
такой вопрос: как сделать, чтобы диод загорался при одновременном нажатии 2х кнопок
Прекрасные уроки, а главное все доходчиво! Хотелось бы увидеть от Вас урок по кнопке с фиксацией с правильным алгоритмом антидребезга.
Доброго дня! Спасибо за интересные уроки.
Вопрос такой: объясните почему выражение for { (!(PINB&0b00000001)) … } нельзя заменить выражением for { (PINB != 0b00000001) … }?
Спасибо за столь высокую оценку ресурса!
Потому что в левом выражении мы проверяем только один бит, а в Вашем правом — все биты байта, получается, что биты от 7 до 1 должны быть обязательно не равны нулю.
Здравствуйте! В ассемблере на PICах я изучал алгоритм кнопки такой: при первом нажатии кнопки светодиод включался, при повторном нажатии выключался. Как этот алгоритм осуществить на Си и для Atmega. Пока не врубаюсь. Если можете помогите пожалуйста.
jurchik323
Используйте режимы и флаги.
долго всматривался и вглядывался… но так и не уловил.. поясните по возможности.
Выполняя вот эту
if (!(PINB&0b00000001))
операцию мы работаем только с одним битом порта В, а конкретно с В0!
тогда почему далее вот тут
{PORTD = 0b00000001;}
else
{PORTD = 0b00000000;}
у нас запись и обнуление идёт полностью на весь порт D? (ведь у нас какието ноги порта D могут быть заняты чем то иным)
как записать единицу отдельно в PD0 понятно. (например PORTD=_BV(DO);)
а вот как обнулить отдельно DO не трогая весь порт я чтото не пойму.
Спасибо.
Здесь нам не важны остальные ножки, были бы важны, обязательно бы прикрутил бы И или ИЛИ.
Ошибка и продолжение:
Забыл вертикальный слеш… конечно же чтобы записать отдельно в PD0 надо
PORTD|=_BV(D0);
А как записать отдель 0(нуль) в этот т же бит не трогая остальные?
PORTD&=~_BV(PD0);
вот так?
Тогда ваша кобинация получится вот такой:
if (!(PINB&0b00000001))
PORTD|=_BV(D0);
else
PORTD&=~_BV(PD0);
Я правильно понимаю?
Спасибо.
вот это _BV не везде работает, можно обойтись и без него
PORTD&=~(0b00000001);
Спасибо за ваши уроки! Только благодаря вам стал немного понимать программирование.
Написал вот такую программу, правда в 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);
}
}
Еще раз спасибо за уроки. Проблему решил.
Теперь новая задача: чтобы после отключения питания и включения вновь автоматически запускался кейс, который работал при выключении.
Я тихо схожу с ума.
Можете не обращать внимание на мои письмена.
Используй еепром и в ней сохраняй номер текущего кейса, а при включении проверяй сохранённое значение ипереходина выполнение нужного кейса.
Здравствуйте.
Скажите пожалуйста, как реализовать возможность регулировки скорости переключения светодиодов например в уроке номер 6 при помощи двух кнопок + и -. В инете ничего не могу найти. Помогите кто нибудь.
По нажатии кнопок меняй время задержки.
Встречал подобную защиту от дребезга,
while (PIND==0b00000000){}
_delay_ms(40);
……..
while (PIND==0b00000001){}
_delay_ms(40);
насколько она актуальна?
Самый актуальный способ подавить дребезг контактов — это триггер Шмитта (SN74HC14N) или специальная логическая ИС Устройство удаления дребезга контактов (MC14490). Да, конечно, требуется дополнительная обвязка, но зато 99.9% надёжно.
Я именно по таймеру и делал всегда, на практике не подводило)
кнопку нужно подключать между резистором, который подключен к +5В и портом контроллера, так при считывании состояния порта контроллер точно будет идентифицировать ее как 1 при отжатой кнопке и как 0 при нажатой.
Однозначно согласен. Еще хорошо себя зарекомендовал керамический конденсатор емкостью 0.22 мкФ параллельно кнопке.
Проверить условие нажатия двух кнопок.