OSBoy notes.

Записки обо всём...

ATtiny13 - включаем/выключаем светодиод по нажатию кнопки

В предыдущем примере мы помигали светодиодом на ATtiny13. Теперь напишем программу, которая будет включать/выключать светодиод по нажатию кнопки. Все опыты я провожу на своей отладочной плате, соответственно код буду приводить применительно к ней.

В принципе, можно в теле самой программы проверять, была ли нажата кнопка, и по нажатию кнопки выполнять определённое действие. Однако, в этом случае фоновая программа будет постоянно прерываться и значительная часть процессорного времени будет тратиться на проверку состояния кнопки, а на практике это не всегда допустимо. Поэтому, рассмотрим сразу другой вариант: с использованием внешних прерываний.
В микроконтроллере ATtiny13 есть два типа внешних прерваний: аппаратное (INT0, на ножке INT0) и программное (PCINT0, на ножках PCINT0-5, которое можно разрешить по маске только на нужных ножках).

INT0 может работать в четырёх рехимах:

  1. По низкому уровню на ножке;
  2. По изменению уровня на ножке;
  3. По спадающему фронту;
  4. По нарастающему фронту.

PCINT0 может работать только по изменению уровня на любой из ножек, настроенных по маске.
Так как ножка INT0 (она же PB1) у нас занята светодиодом*, а кнопки у нас подключены к ножкам PCINT3, PCINT4 (PB3 и PB4 соответственно), будем использовать программное прерывание.

/*
 * tiny13_board_switch
 * Демо-прошивка отладочной платы на ATtiny13.
 * Включаем/выключаем светодиод по внешнему прерыванию
 * от нажатия кнопки.
 */ 

#define F_CPU 1200000UL
#define LED PB2
#define BUTTON1 PB3 // PCINT3
#define BUTTON2 PB4 // PCINT4

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

// Обработчик прерывания PCINT0
ISR(PCINT0_vect)
{
   _delay_ms (50); // антидребезг (использовать задержки в прерываниях некошерно, но пока и так сойдёт)
  if ( (PINB & (1<<BUTTON1)) == 0 || (PINB & (1<<BUTTON2)) == 0 ) // если нажата одна из кнопок
  {
    PORTB ^= (1<<LED); //переключаем состояние светодиода (вкл./выкл.)
    while ( (PINB & (1<<BUTTON1)) == 0 || (PINB & (1<<BUTTON2)) == 0 ) {} // ждём отпускания кнопки
  }
}

int main(void)
{

  // Пины кнопок
  DDRB &= ~((1<<BUTTON1)|(1<<BUTTON2)); // входы
  PORTB |= (1<<BUTTON1)|(1<<BUTTON2); // подтянуты
  // Пин светодиода
  DDRB |= (1<<LED); // выход
  PORTB &= ~(1<<LED); // выключен
  // Настройка прерываний
  GIMSK |= (1<<PCIE); // Разрешаем внешние прерывания PCINT0.
  PCMSK |= (1<<BUTTON1)|(1<<BUTTON2); // Разрешаем по маске прерывания на ногак кнопок (PCINT3, PCINT4)
  sei(); // Разрешаем прерывания глобально: SREG |= (1<<SREG_I)
    while (1) 
    {
    }
}

В начале программы мы указали тактовую частоту процессора, указали к каким ножкам у нас подключены кнопки и светодиод, которым мы хотим управлять.
Затем, подключили необходимые заголовочные файлы. Тут, помимо прочего, мы подключили библиотеку avr/interrupt.h, которая отвечает за работу прерываний.

Функция ISR(PCINT0_vect) - это обработчик прерывания PCINT0. (вектор соответствующего прерывания указывается как аргумент, в скобках). Данная функция будет выполняться каждый раз при срабатывании прерывания PCINT0.

Дальше у нас идёт основное тело программы: функция main(). В ней мы первым делом настраиваем пины ввода/вывода:
Пины, к которым подключены кнопки, сконфигурируем как входы:

DDRB &= ~((1<<BUTTON1)|(1<<BUTTON2));

И устанавливаем на входах по умолчанию высокий логический уровень:

PORTB |= (1<<BUTTON1)|(1<<BUTTON2);

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

DDRB |= (1<<LED);
PORTB &= ~(1<<LED);

Затем настраиваем прерывания:
Разрешим прерывание PCINT0. За это отвечает бит PCIE регистра GIMSK. Поэтому записываем в него единицу:

GIMSK |= (1<<PCIE);

Установкой битов PCINT0-5 регистра PCMSK можно задать, при изменении состояния каких входов будет срабатывать прерывание PCINT0:

PCMSK |= (1<<BUTTON1)|(1<<BUTTON2);

И, наконец, мы должны разрешить прерывания глобально. Это делается установкой флага SREG_I в регистре SREG:

SREG |= (1<<SREG_I);

Или можно использовать макрос, который подставит ассемблерную команду SEI:

sei();

Далее, мы видим, что бесконечный цикл у нас пустой, то есть в фоне никаких задач не выполняется, т.к. нашу задачу включения-выключения светодиода мы реализовали на прерываниях.

Прерывания у нас будут обрабатываться следующим образом:
При нажатии на любую из управляющих кнопок срабатывает прерывание PCINT0 и вызывается функция-обработчик прерывания ISR(PCINT0_vect).
В теле обработчика будет происходить следующее.
Чтобы отфильтровать дребезг контактов кнопки, выдерживаем паузу 50 мс **. Это нужно для того, чтобы прерывание не сработало несколько раз подряд при однократном нажатии. 
Т.к. прерывание PCINT0 наступает при любом изменении логического уровня на входе, то нужно в обработчике проверять, была ли кнопка нажата, либо отпущена. Если любая из кнопок нажата, то выполняем нужное действие (инвертируем состояние выхода, к которому подключен светодиод):

PORTB ^= (1<<LED);

После этого ожидаем, пока обе кнопки не будут отпущены**:

while ( (PINB & (1<<BUTTON1)) == 0 || (PINB & (1<<BUTTON2)) == 0 ) {}

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

* Несмотря на то, что к ножке INT0 на моей плате подключен светодиод, ничто не мешает снять его джампер и джампер любой кнопки, после чего подключить кнопку к ножке INT0 проводом-перемычкой. При таком подключении можно будет использовать аппаратное прерывание INT0 по нажатию кнопки.

** Обработчики прерываний должны выполняться как можно быстрее. Использовать задержки в прерываниях не рекомендуется и считается дурным тоном, т.к. при этом приостанавливается выполнение фоновых задач. То же самое касается и ожидания отпускания кнопки в нашем примере! Но, поскольку это всего лишь простой пример и в фоне у нас ничего не выполняется, то в данном случае можно оставить и так. Вообще, борьба с дребезгом контактов - это целая отдельная тема и вариантов тут может быть масса, как программных, так и аппаратных... Но пока, для данного конкретного случая, программно мне удалось добиться наиболее чёткого однократного срабатывания на нажатие кнопки именно способом, описанным выше: с помощью задержки и последующего ожидания отпускания...

Полезные ссылки:
ATtiny13 Datasheet (официальный)
ATtiny13 Datasheet (урезанный вариант)
EasyElectronics - Программирование на Си. Часть 3: Прерывания

Теги : attiny13, светодиод, кнопка, внешние прерывания, AVR, микроконтроллеры, программирование, си, C

Комментариев: 25

SDRV
#Ответить
Сопровождение ПО - просто шик!)
Роман
#Ответить
А как сделать что бы вторая кнопка просто выключала светодио?
OSBoy
#Ответить
Не совсем понял суть вопроса... Она же итак его и включает и выключает. Поточнее объясните, что вы хотите сделать? Чтобы одна кнопка только включала, а вторая - только выключала?
ilil
#Ответить
здравствуйте.нужен сенсорный модуль на 3 кнопки на ATtiny13.в инете есть схема на сайте паяльник на 1 2 3 кнопки с открытым кодом.сам не разбираюсь в написании прошивки собрать могу.сделал плату прошил 3 микроконтроллера фьюзы выставлял правильно-не работает.пришлось восстанавливать доктором.нажал 1 отпустил 0-такой алгоритм.не могли ли вы мне помочь?
Игорь
#Ответить
Очень красивый и техничный пример
Я понимаю что синтаксис конечно это немного не сюда
Но я бы дополнил бы пример пожалуй возможностью исполнения цикла при условии а не одной операции
К примеру
if ( (PINB & (1<<BUTTON1)) == 0 || (PINB & (1<<BUTTON2)) == 0 ) // если нажата одна из кнопок
{
while ( (PINB & (1<<BUTTON1)) == 0 || (PINB & (1<<BUTTON2)) == 0 ) {
PORTB I= (1<<LED);
_delay_ms (50)
PORTB &=~ (1<<LED);
_delay_ms (50)
} // мигаем лампочкой пока кнопка нажата
}
}
Зы
Я ни в коем случае не умничаю
просто так было бы полнее чтоле
В любом случае спасибо за статью :)
labeanchik
#Ответить
А мне нужно наоборот, если кнопка нажата, то
выключить один светодиод,
подождать некоторое время
и включить другой светодиод, не ожидая, пока кнопка будет отжата.
Как бы 1 раз нажал и все, Вот как такое сделать ?
Игорь
#Ответить
Ок
А дальше то что
Диод так и остаётся гореть ? чем всё заканчивается ?
Для этого даже не нужен микроконтроллер
Реле + конденсатор + резистор + диод :)
labeanchik
#Ответить
Мне нужно микроконтроллером управлять 8 каналами реле.
Есть 8 кнопок и 8 выходов на реле.
Каждая кнопка включает свой выход и выключает остальные.
Так вот, нужно, чтобы кнопка при повторном нажатии ничего не выполняла, пока включен канал назначенный включением этой кнопки.
OSBoy
#Ответить
Какой МК будете использовать? Кроме управления реле, он что-нибудь ещё будет делать? Может набросаю вам пример на досуге...
labeanchik
#Ответить
Только реле.
Я уже вроде сам сделал. Конечно не шарю в программировании, но что получилось - вроде работает.
Т.е. при повторном нажатии не перенажимается, а просто остается в том же состоянии выход.
Еще вроде работает так, что если зажать несколько кнопоко одновременно, то будет работать первая нажатая, пока не отожму ее.
Ну короч, посмотрите, посмейтесь с моего кода:)
my-files. su/dqp0su
Ну и так же на каждую кнопку. Правда так много кода - ну это потому что не шарю я в этом.
OSBoy
#Ответить
Я не совсем понял, для чего в Вашем коде используется PORTA (POWER). А для восьми кнопок и светодиодов (реле) по Вашему описанию выше, я бы сделал примерно так: my-files. su/aqsc1a
(В работе я не проверял, можете затестить при желании.)
labeanchik
#Ответить
кол-ва PORTD же не хватает
PORTD и PORTA на реле.
7 ног PORTD и 1 нога от PORTA.
А PORTB 8 ног.
Микроконтроллер же Attiny2313.
Если честно. что мне ваш вариант пока не понятен.
Мне еще не понятны все выражения и служебные.
Надо разбираться.
OSBoy
#Ответить
Поэтому я сразу и спрашивал какой МК. Тогда вот что у меня получилось: my-files. su/v7i8wm
Kirill
#Ответить
Здравствуйте. Подскажите на чем можно реализовать следующую задачу:
Три кнопки без фиксации, три транзистора которые включают 3 лазерных светодиода. Нужно чтобы каждая кнопка включала\выключала свой светодиод
OSBoy
#Ответить
Здравствуйте. Да в принципе, почти любой МК семейства attiny/atmega из того, что найдётся под рукой - лишь бы портов ввода/вывода хватило: три на входы (кнопки), три - на выходы (транзисторы)... Можно и на tiny13 сделать, но ног у неё в обрез - придётся reset использовать - а это некоторым геморроем чревато... поэтому лучше взять что-нибудь с бОльшим количеством ног, ну хотя бы тот же Attiny2313 - у него ног под Ваши цели достаточно...
Игорь
#Ответить
А зачем?
D тригер в помощь
Какой нить к155тм2 будет вполне достаточно
Артем Александрович Демчук
#Ответить
Здравствуйте.
Хочу сделать режим сна в паяльник Sh-72. Суть похожа на переключение состояния светодиода по кнопке, только там я буду подавать и отключать питание на микросхеме внутри паяльника.
Думаю взять вашу прошивку, но вместо кнопки будет датчик наклона.
В чем суть: когда датчик наклона замкнут - напряжение будет поступать через микроконтроллер на микросхему, как только датчик разомкнутся - начнется обратный отсчёт (3 минуты желательно), по истечение которого микроконтроллер перестанет подавать напряжение на микросхему. Потом при замыкании датчика напряжение сразу же начнет поступать через микроконтроллер на микросхему. И все должно повторяться. Надеюсь объяснил понятно.
Но есть одно но, я ещё не умею программировать и писать прошивки для микроконтроллеров.
Надеюсь на чью нибудь помощь!
Заранее спасибо!
OSBoy
#Ответить
Вот ссылки на прошивку для attiny13 (hex файл) и исходник программы. (пробелы после точки нужно убрать) :
my-files. su/h2a0up
my-files .su/2jhjhq
Прошивка рассчитана на работу МК с частотой 1,2МГц.
Как прошить и выставить частоту МК, думаю сами нагуглите. Если у Вас МК будет работать на другой частоте, тогда в программе соответственно нужно будет цифры подправить и перекомпилировать прошивку (там чистая математика, я всё достаточно подробно в коде расписал в комментариях).

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

Отменить