Здравствуйте, давайте добавим кнопку к микроконтроллеру ATmega88.
Схема подключения кнопки к микрочипу:
Кнопку к микроконтроллеру можно подключить двумя основными способами, с подтяжкой линии порта к высокому логическому уровню или низкому через резистор, как показано на картинке ниже.
Я обычно использую первый вариант подключения, можно конечно использовать внутренние подтягивающие резисторы на входах PORTB, но мне еще не доводилось применять такой способ.
Также подтягивающий резистор в несколько десятков килоом, который можно не паять физически, а подключить его внутри МК специально предназначенной для этого командой.
Схема подключения для нашего варианта:
Используем традиционный способ подключения, подсоединив нормально разомкнутую кнопку без фиксатора между выводом PB1 и землёй. Таким образом, эта кнопка при нажатии будет замыкать вывод PB1 на низкий (нулевой) уровень напряжения.
А для того, чтобы при разомкнутой кнопке данный вход микроконтроллера не болтался в воздухе, а был притянут к питанию, мы применим подтягивающий резистор в несколько десятков килоом, который можно не паять физически, а подключить его внутри МК специально предназначенной для этого командой.
Светодиод, как и ранее, подключим через резистор к выводу Atmega8 PC0.
Ну что ж, пришло время Atmel Studio и программы прошивки, которую будем писать всё на том же языке Си. Для начала – всё по аналогии с предыдущим проектом:
#include <avr/io.h> #include "delay.h" #include "delay.c" #define F_CPU 16000000UL // 1 MHz clock speed int main(void) { // Начало основной программы
Теперь нам надо настроить порт PC0 – как выход и установить на нём низкий уровень, чтобы при включении питания светодиод был погашен:
DDRC |= ( 1 << 0 ); // Конфигурируем вывод порта PC0 - как выход, PORTC &= ~(1 << PC0); // Устанавливаем 0 на его выходе
PB1 настраиваем как вход, причём не простой вход, мотающийся абы как в воздухе, а притянутый внутренним резистором к шине питания:
DDRB &= ~( 1 << 1 ); // Конфигурируем вывод порта PB1 - как вход PORTB |= ( 1 << 1 ); // Подключаем к PB1 подтягивающий резистор на плюс питания
Переключение светодиода, т. е. изменение выходного уровня на PC0 должно происходить в момент замыкания копки (по отрицательному фронту сигнала на PB1). Всё остальное время, причём не сильно важно: замкнута ли кнопка или разомкнута, МК не должен совершать никаких действий с PC0, мало того, не должен останавливать выполнение циклов основной программы.
И поскольку нам надо отслеживать перепад уровня сигнала на входе PB1, а не сам уровень, то нам потребуется некая дополнительная переменная, назовём её “in”. Эта переменная будет запоминать уровень входного сигнала в конце предыдущего цикла, а программа будет сравнивать её значение с текущим состоянием входа PB1. Поскольку начальное напряжение на PB1 (при ненажатой кнопке) равно напряжению питания, то и начальное значение этой переменной объявим равным единице.
char in = 1; // Объявляем переменную in и записываем в неё число 1
Теперь, когда все подготовительные работы у нас проведены, можно запускать основную программу:
while { // начало цикла
и в каждом цикле ожидать перепада напряжения на PB1, соответствующего моменту замыкания кнопки.
Такое у нас произойдёт при PB1 = 0 и in = 1. А условие такого ожидания будет иметь вид:
if (!(PINB & (1<<PINB1)) && in == 1 ) /* если PB1 стал равен нулю и значение in при этом = 1, то мы поймали замыкание кнопки на входе, т. е. перепад из 1 в 0 */ {
Что нам теперь нужно сделать, после того как мы определили момент нажатия кнопки?
1. Присвоить переменной in = 0, потому как уровень входного сигнала на PB1 у нас стал нулевым.
2. Подождать 10…100 мс (в зависимости от качества кнопки), пока не закончится дребезг контактов.
3. Переключить выход PC0 в противоположное состояние, т. е. если на выходе была единица – скинуть её в ноль и наоборот.
Сделаем мы это посредством следующих команд:
in = 0; // 1. Стало быть уровень входного сигнала стал равен нулю _delay_ms(100); // 2. Задержка 100мс. Выжидаем окончания дребезга контактов PORTC ^= (1 << 0); // 3. Переключаем PC0 в противоположное состояние }
Момент замыкания отработан, цикл продолжает крутиться, не мешая выполнению команд, находящихся внутри программы. А наша часть программы, отвечающая за обработку состояния входа, ждёт появления положительного перепада (из 0 в 1) на PB1, который будет соответствовать отпусканию, т. е. размыканию кнопки.
Такое произойдёт при условии PB1 = 1 и in = 0, то есть:
if (PINB & (1 << PINB1) && in == 0) /* если PB1 стал равен единице и in = 0, то мы поймали на входе размыкание кнопки, т. е. перепад из 0 в 1 */ {
Что делать, после того, как мы определили момент отпускания кнопки?
1. Присвоить переменной in = 1, потому как уровень входного сигнала на PB1 у нас вернулся к единице.
2. Опять подождать 10…100 мс, пока не закончится дребезг контактов.
А больше ничего делать и не надо, т. к. выход у нас должен переключаться только при замыкании кнопки.
Опишем эти процедуры командами:
in = 1; // В этот момент уровень входного сигнала стал равен нулю _delay_ms(100); /* Задержка 100мс - опять ждём окончания дребезга, больше ничего в момент размыкания кнопки делать не будем */ }
Всё, что теперь остаётся – это опять вернуться к началу цикла и продолжить выполнение программы
} // конец цикла } // конец программы
Теперь сгруппируем всё воедино и получим искомый код на языке Си:
#include <avr/io.h> #include "delay.h" #include "delay.c" #define F_CPU 16000000UL // 1 MHz clock speed int main(void) { // Начало основной программы DDRC |= ( 1 << 0 ); // Конфигурируем вывод порта PC0 - как выход PORTC &= ~(1 << PC0); // Устанавливаем 0 на его выходе DDRB &= ~( 1 << 1 ); // Конфигурируем вывод порта PB1 - как вход PORTB |= ( 1 << 1 ); // Подключаем к PB1 подтягивающий резистор на плюс питания char in = 1; // Объявляем переменную in и записываем в неё число 1 while (1) { // начало цикла if (!(PINB & (1<<PINB1)) && in == 1 ) /* если PB1 стал равен нулю и значение in при этом = 1, то мы поймали замыкание кнопки на входе, т. е. перепад из 1 в 0 */ { in = 0; // 1. Стало быть уровень входного сигнала стал равен нулю _delay_ms(100); // 2. Задержка 100мс. Выжидаем окончания дребезга контактов PORTC ^= (1 << 0); // 3. Переключаем PC0 в противоположное состояние } // Теперь ждём момента, когда копка будет отпушена: if (PINB & (1 << PINB1) && in == 0) /* если PB1 стал равен единице и in = 0, то мы поймали на входе размыкание кнопки, т. е. перепад из 0 в 1 */ { in = 1; // В этот момент уровень входного сигнала стал равен нулю _delay_ms(100); /* Задержка 100мс - опять ждём окончания дребезга, больше ничего в момент размыкания кнопки делать не будем */ } } // конец цикла } // конец программы
Скачать hex файл с кнопкой для Atmega88:
– в zip архиве.
– в tr.gz архиве.
Примечание:
Другие примеры кода с кнопкой:
Пример 1: До нажатия кнопки – светодиод мигает на ножке PC0, также светодиоды горят на ножках PC1 и PC2. При нажатии на кнопку – светодиоды на PC0, PC1, PC2 – гаснут, а на PC3, PC4, PC5 – зажигаются на несколько секунд, а после всё возвращается в начальное состояние – до нажатия кнопки.
Кнопка расположена на PB1:
#include <avr/io.h> #include "delay.h" #include "delay.c" #define F_CPU 16000000UL // 1 MHz clock speed int main(void) { DDRC = 0xFF; //Конфигурируем PORTB на выход DDRB &= ~( 1 << 1 ); // Конфигурируем вывод порта PB1 - как вход PORTB |= ( 1 << 1 ); // Подключаем к PB1 подтягивающий резистор на плюс питания char in = 1; // Объявляем переменную in и записываем в неё число 1 while(1) //infinite loop { PORTC = 0b0000111; _delay_ms(100); PORTC = 0b0000110; _delay_ms(100); if (!(PINB & (1<<PINB1)) && in == 1 ) /* если PB1 стал равен нулю и значение in при этом = 1, то мы поймали замыкание кнопки на входе, т. е. перепад из 1 в 0 */ { in = 0; // 1. Стало быть уровень входного сигнала стал равен нулю _delay_ms(100); // 2. Задержка 100мс. Выжидаем окончания дребезга контактов PORTC = 0b0111000; // 3. Переключаем PC0 в противоположное состояние _delay_ms(1000); } // Теперь ждём момента, когда копка будет отпущена: if (PINB & (1 << PINB1) && in == 0) /* если PB1 стал равен единице и in = 0, то мы поймали на входе размыкание кнопки, т. е. перепад из 0 в 1 */ { in = 1; // В этот момент уровень входного сигнала стал равен нулю _delay_ms(100); /* Задержка 100мс - опять ждём окончания дребезга, больше ничего в момент размыкания кнопки делать не будем */ } } }
Скачать файлы примера 1:
– скачать в формате zip
– скачать в формате tar.gz
Пример 2:
#include <avr/io.h> #include "delay.h" #include "delay.c" #define F_CPU 16000000UL // 1 MHz clock speed int main(void) { DDRC = 0xFF; //Nakes PORTB as Output // Устанавливаем 0 на его выходе DDRB &= ~( 1 << 1 ); // Конфигурируем вывод порта PB1 - как вход PORTB |= ( 1 << 1 ); // Подключаем к PB1 подтягивающий резистор на плюс питания char in = 1; // Объявляем переменную in и записываем в неё число 1 while(1) //infinite loop { PORTC = 0b0000111; if (!(PINB & (1<<PINB1)) && in == 1 ) /* если PB1 стал равен нулю и значение in при этом = 1, то мы поймали замыкание кнопки на входе, т. е. перепад из 1 в 0 */ { in = 0; // 1. Стало быть уровень входного сигнала стал равен нулю _delay_ms(100); // 2. Задержка 100мс. Выжидаем окончания дребезга контактов PORTC = 0b0111000; // 3. Переключаем PC0 в противоположное состояние _delay_ms(500); } // Теперь ждём момента, когда копка будет отпушена: if (PINB & (1 << PINB1) && in == 0) /* если PB1 стал равен единице и in = 0, то мы поймали на входе размыкание кнопки, т. е. перепад из 0 в 1 */ { in = 1; // В этот момент уровень входного сигнала стал равен нулю _delay_ms(100); /* Задержка 100мс - опять ждём окончания дребезга, больше ничего в момент размыкания кнопки делать не будем */ } } }