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

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

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

Отменить