Подключение кнопки к AVR
Управляющая программа обработки нажатия кнопки.
Подключаем кнопку к микроконтроллеру Atmega8, устраняем дребезг контактов, зажигаем и гасим светодиод.
Как правило, большинство изделий, построенных на микроконтроллере, не обходится без какого-либо коммутирующего элемента, который в
простейшем виде может представлять собой обычную кнопку без фиксации. А практически любая кнопка – это источник "дребезга",
представляющего собой процесс многократного паразитного замыкания и размыкания контактов в моменты переключения.
Как справиться с этим вредным эффектом аппаратными средствами, мы подробно рассмотрели на странице –
Ссылка на страницу.
Сегодня же мы проделаем всё то же самое, но только программными методами, т. е. без использования каких-либо внешних элементов.
По традиции начнём с конца, т. е. со схемы уже готового проекта, собранного на Atmega8 в Протеусе (Рис.1). Далее нам предстоит
написать программу прошивки микроконтроллера в Atmel Studio, а потом проверить всё это хозяйство в симуляторе.

Рис.1 Кнопка управляет Atmega8 – зажигаем и гасим светодиод
Используем традиционный способ подключения, подсоединив нормально разомкнутую кнопку без фиксатора между выводом PB1 и землёй.
Таким образом, эта кнопка при нажатии будет замыкать вывод PB1 на низкий (нулевой) уровень напряжения.
А для того, чтобы при разомкнутой кнопке данный вход микроконтроллера не болтался в воздухе, а был притянут к питанию, мы применим
подтягивающий резистор в несколько десятков килоом, который можно не паять физически, а подключить его внутри МК специально
предназначенной для этого командой.
Светодиод, как и ранее, подключим через резистор к выводу Atmega8 PC0.
Ну что ж, пришло время Atmel Studio и программы прошивки, которую будем писать всё на том же языке Си.
Для начала – всё по аналогии с предыдущим проектом:
#include <avr/io.h>
#define F_CPU 1000000UL // Выбираем частоту МК
#include <util/delay.h> // Включаем функцию задержек
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мс - опять ждём окончания дребезга,
больше ничего в момент размыкания кнопки делать не будем */
}
Всё, что теперь остаётся – это опять вернуться к началу цикла и продолжить выполнение программы
} // конец цикла
} // конец программы
Теперь сгруппируем всё воедино и получим искомый код на языке Си:
/*
* GccApplication2.c
*
* Created: 22.06.2021 11:37:14
* Author : Vpayaem.ru
*/
#include <avr/io.h>
#define F_CPU 1000000UL // Выбираем частоту МК
#include <util/delay.h> // Включаем функцию задержек
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мс - опять ждём окончания дребезга,
больше ничего в момент размыкания кнопки делать не будем */
}
} // конец цикла
} // конец программы
Теперь, так же как и в предыдущем примере, нужно перенести готовый код в Atmel Studio и скомпилировать его. Для этого необходимо
кликнуть по кнопке Build и в выпавшем меню выбрать Build Solution.
Если ошибок нет, то файл успешно скомпилируется, а в нижней части экрана появится надпись:
==== Build: 1 succeeded or up-to-date, 0 failed, 0 skipped ====
Далее нам надо войти в папку, в которой мы сохранили наш проект, найти там ещё одну папку с названием Debug и убедиться в
существовании файла с расширением HEX.
При помощи этого файла может производиться как прошивка микроконтроллера, так и проверка его
работоспособности в программе для автоматизированного проектирования Proteus.
Скачать файл knopka.hex можно по ссылке – скачать файл
|