Внешние прерывания у 8-bit avr, использование кнопок

Здравствуйте, на днях решил поэксперементировать с внешними прерываниями на attiny2313A. Думаю тем кто занимался программированием микроконтроллеров известно, что МК не всегда быстро может реагировать на нажатие кнопки, т.к. проверка PINа стандартно осуществляется в бесконченом цикле и если программа доостаточно большая — это может затормозить опрос ножки.
Код ниже написан для WinAVR.

image

Стандартный опрос:


  • DDRxy&=~(1<<y); PORTxy&=~(1<<y);
    с одной стороны кнопка подключена к плюсу (VCC), а с другой стороны к ножке.
    В таком случае провод который подключен к ножке выступает в роли антены и любое возмущение электрическо-магнитного поля вокруг проводка вызывает срабатываение кнопки, что неприемлимо.

  • DDRxy&=~(1<<y); PORTxy|=(1<<y);
    С одной стороны кнопка подключена к минусу (GND), а с другой стороны к ножке.
    Это наиболее примелимый вариант, наводок не возникает и кнопка срабатывает стабильно.



Во втором способе, обычный опрос в цикле будет выглядеть:
if (!(PINB & (1<<PINB6))){ // если нажата кнопка на ноге PORTB6, то выполнить:
моргнуть;
}
if (PINB & (1<<PINB7)){ // если ненажата кнопка на ноге PORTB7, то выполнить:
моргнуть;
}

если инвертировать, то получиться и для первого способа, но не думаю что это кому-то надо.

Итак сами Внешние прерывания


В даташите на Мк attiny2313A
есть прерывания INT0 (нога PD2), INT1(нога PD3), и PCINT0..7 (весь порт B и хотя ножек у него 8, но прерывание одно на всех, что лично меня не радует )
Прерывания INT0 и INT1 их приоритет выше других прерываний.

Итого мы можем настроить всего три кнопки без использования стандартного опроса.
У меня была мысль, что т.к. я задействую внешние прерывания то регистры DDR и PORT ничего не изменят в поведение МК, но это не так… выход так же надо настраивать как при стандартной обработке.
Сразу оговорюсь, что я пишу про ножки, настроенные на подтягивающие резисторы т.е. DDRxy&=~(1<<y); PORTxy|=(1<<y). иначе смысла нет, срабатывание от прикосновения пальца, как мне кажется, никого не интересует.

Регистры управления


Итак даташит страница 58 External Interrupts

MCU Control Register– MCUCR:
image

image
image

Если стоит по дефолту, The low level of INT0 generates an interrupt request, то нажав кнопку мы получим срабатвыание вектора, если ее не отпускать, то программа снова и снова будет уходить на прерывание.

Если Any logical change on INT1 generates an interrupt request, то нажав кноку и не отпуская ее, функция прерывания сработает один раз и дальше программа пойдет по стандартной схеме, но когда мы подождав отпутим кнопку — это опять же будет изменение логики, то вектор опять запуститься.

The falling edge of INT1 generates an interrupt request — тоже самое что и дефолт, во всяком случае по эксперементам, только работает мене стабильно, пока отжимаешь кнопку может сработать.

The rising edge of INT1 generates an interrupt request — кнопка работает только когда ее отжимаешь.

General Interrupt Mask Register – GIMSK:
Глобально разрешает нужные нам прерывания.
image

External Interrupt Flag Register – EIFR:
Регситр отвечающий за испольнение прервыания, если логика на ножке изменилась, то в регистре появляется запись, и вектор прервыания начинает обрабатываться.
image

Pin Change Mask Register – PCMSK:
Разрешает прерывание на той или иной ноге Порта B
image

отдельно про PCINT

Это ненастраиваемое внешнее прерывание, в отличие от других и всегда работает по принципу Any logical change что несколько затруднит его использование, хотя и ему применение найдется в разумных руках.
И еще беда одна — наблюдается ложное срабатывание при подключении МК к питанию, уходит один раз на прерывание и больше не сбоит, работая в штатном режиме.

Дополнение от Ocelot( в комментариях):
Нет большой проблемы в том, что прерывание срабатывает по любому изменению уровня (any logical change). В обработчике прерывания всегда можно проверить состояние порта, и определить нужное нам событие. То же самое касается прерывания PCINT, которое одно на все 8 ног. Всегда можно легко узнать, какой именно вход вызвал прерывание.

Вектора прерываний затрагивающие нашу тему


image

void preriv() //функция инициализации прерываний
{
GIMSK=(1<<PCIE)|(1<<INT0);
PCMSK=(1<<PCINT0);
MCUCR=(0<<ISC00)|(0<<ISC01);
}
preriv();// вызов функции инициализации в теле цикла

ISR(INT0_vect){ // прерывание по вектору INT0
PORTD|=(1<<PORTD4);
_delay_ms(1000);
}

ISR(PCINT_vect){ // прерывание по вектору PCINT
PORTD|=(1<<PORTD4);
_delay_ms(1000);
}


Проект для Win AVR, attiny2313 PORTD4 — на нем стоит анодом(плюсом) светодиод и моргает когда кнопка на POTRD2 замыкается на землю.

Чтобы использовать другой МК смотрите соответствующий Даташит.

Самому мне помогла статья Dmitry.
Поделиться публикацией

Комментарии 22

    +3
    Нет большой проблемы в том, что прерывание срабатывает по любому изменению уровня (any logical change). В обработчике прерывания всегда можно проверить состояние порта, и определить нужное нам событие. То же самое касается прерывания PCINT, которое одно на все 8 ног. Всегда можно легко узнать, какой именно вход вызвал прерывание.
      0
      Наверное, но насколько я смотрел уже в атмеге8 подобия PCINT уже нет, и заморачиваться с ним не каждый захочет, я просто обозначил проблему. спс за комент дополню сейчас статью.
        0
        Зато в более новых сериях 48/88/168 — есть.

        Если не ошибаюсь, тини2313 тоже моложе меги8
          0
          Действительно щас залез в даташиты… PCINT у 48/88 на все ножки) и прерываний на них всех не одно, а 4 =)
        0
        а как узнать какой вход вызвал прерывание?
        как узнать состояние порта?
        0
        Что такое 0<<y?
          0
          т.к. порты у МК могут быть разными я взял их за переменные X-название порта, Y- его номер. Если что-то непонятно, то винзу есть примеры рабочего кода.
            +1
            Вот это:
            DDRxy |= (0<<y);

            не изменит значения DDR. Если нужно сбросить разряд в ноль, делают
            DDRxy &= ~(1<<y);
              +1
              эм..=) ты прав, но я как бы не сбрасывал, а показывал состояние порта которое должно быть, оно и так по дефолту нулю равно, ща подумаю как бы поточнее описать и переделаю начало.
                0
                Описать состояние разряда можно так:
                DDRxy & (1<<y) == 1;
                или
                DDRxy & (1<<y) == 0;
          +5
          ИМХО, нет нужды использовать прерывания для опроса кнопок, за исключением тех случаев, когда клавиатура выводит микроконтроллер из режима сна. Опрос кнопок — это самый некритичный ко времени выполнения процесс, и его всегда можно запихнуть в бекграунд вместе с алгоритмом подавления дребезга контактов.

          "… МК не всегда быстро может реагировать на нажатие кнопки, т.к. проверка PINа стандартно осуществляется в бесконченом цикле и если программа доостаточно большая — это может затормозить опрос ножки… "
          — справедливо только для быдлокодеров.

          На сайте Atmel есть замечательные апноуты, которые закрывают абсолютно все вопросы по поводу подключения кнопок к микроконтроллеру — AVR243: Matrix Keyboard Decoder on tinyAVR and megaAVR devices, AVR245: Code Lock with 4x4 Keypad and I2C LCD on a tinyAVR, а также AVR252: TV Control Touch Keyboard (с примерами исходного кода).
            0
            По мне есть вопрос времени в кодинге, можно подстроить сложный код под опрос, тогда действительно получается некритично, но на это надо затратить силы и время, когда как прерывание внешнее, которое уже предусмотрено производителем можно заюзать сразу, зная как оно будет себя вести и код доделывать под опрос не надо, конечно это одна из самых простых задач в программинге МК, но и на этом можно время сэкономить, я же не говорю что это панацея) кому нужно — попробуют.
            0
            Эх, пунктуация…
              0
              … и опечатки…
              +2
              Не вижу никакой проблемы в скорости реакции на кнопки. Обычно, если МК выполняет более одной задачи, я предпочитаю решение с организацией тасков по таймеру. Опрос клавиатуры, например, можно делать фиксированно каждые 0.25 секунд, тогда с учетом устранения дребезга, опрос будет стабильно раз в 0.5 сек.
                0
                Мой скромный опыт подсказывает, что кнопки и прерывания несовместимы (за редким исключением): задержки в прерываниях (тем более в AVR, где нет приоритетов прерываний) идея крайне плохая — по вашей версии кода, после нажатии кнопки в течении секунды никакие другий прерывания обрабатываться не будут, главный цикл программы так же не будет выполнятся. Если активирован watchdog это гарантированно приведет к сбросу МК. Вообще такой подход, кроме как к миганию светодиодом более ни к чему не применим.
                Механический конкакт (кнопка) и прерывания — так же плохо. Попробуйте, на досуге посчитать сколько прерываний приходит при нажатии, несколько сотен, минимум. А со временем (при износе кнопки) — на порядок больше
                  0
                  да, но простые программки, где не хочется делать счетчики, легко строются на принципе внешних перерываний, и то что тормозиться основноой цикл и др важные прерывания в основном не так уж и страшно.
                    0
                    Можно ездить по встречке, если задом: до определенного момента никто слова не скажет, но ведь неудобно же.
                    В обработчиках прерываний даже делить целые числа не стоит, не то что специально задержки делать. А обработать состояние пина, даже с антидребезгом проще по-таймеру, или в цикле в main()

                    Я пытался придумать, хоть одну задачку, где можно использовать ваш способ, но кроме моргнуть светодиодом ничего не придумалось. Подскажите?
                    0
                    а что мешает после срабатывания прерывания запустить проверу на дребезг? (постоянность принятого сигнала на протяжении скольких-то мс, чтоб не глючило на этот момент запретить это прерываение..)
                      0
                      Пока обрабатывается прерывания другие ждут в очереди, приоритетов в AVR нет. Поэтому выходить из прерывания нужно как можно быстрее иначе можно пропустить что-то важное.
                      Проще опрашивать кнопку по таймеру, если нажата инкрементировать какой-нить счетчик, если отпущена — сбрасывать. Как только счетчик дойдет до какого-то значения — обрабатываем нажатие. Чем плохо?
                    0
                    Прерывания еще клево использовать для экономии электроэнергии — например, повесить приемную ногу uart на INT и когда пойдут данные то контроллер пробудится и сможет принять данныые. Есть несколько режимов сна, есть те которые позволяют не терять данные в приемном буффере…
                      0
                      С описанием настройки в регистре MCUCR вы усложнили. И на чем это вы программу писали (просто я только с ассемблером работал)?

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.