Подключаем кнопку к Atmega88

Здравствуйте, давайте добавим кнопку к микроконтроллеру 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мс - опять ждём окончания дребезга,
больше ничего в момент размыкания кнопки делать не будем */
}
}
}

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