Обработка нажатия пользовательской кнопки, используя внешние прерывания

Обработка большого количества кнопок на одном прерывании

Источник: http://AVRproject.ru/publ/poleznaja_informacija/obrabotka_bolshogo_kolichestva_knopok_na_odnom_preryvanii/4-1-0-138

Разбираемся с кнопкой

Мы уже познакомились с понятием прерывания (и даже использовали таблицу векторов прерываний) для реализации задержки с SysTick.

Теперь же нам предстоит работать с кнопкой, и если с точки зрения программной реализации нам всё понятно – действия аналогичны тому, как мы работали со светодиодом (только в этом случае мы не пишем в ODR, а читаем из IDR – если вы делаете это в основном цикле), – то с прерываниями не всё так просто.

Как отмечалось раньше, они бывают двух видов: внутренние и внешние по отношению к ядру. Так как SysTick – составная часть ядра, то и прерывание от него реализовано по-другому (вы включаете его непосредственно из регистров SysTick).

В случае с GPIO (или любой другой периферией) это происходит через модуль внешних прерываний EXTI (англ. external interrupt). Но перед тем, как мы начнем разбираться с ним, давайте настроим ножку, используя библиотеку StdPeriph.

Исходя из принципиальной схемы устройства, ножка, к которой подсоединена кнопка – PA3. Добавим функцию инициализации в файл button.c

void init_button() { RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // PA3 – floating input GPIOA->CRL &= ~GPIO_CRL_MODE3; GPIOA->CRL |= GPIO_CRL_CNF3_1; GPIOA->CRL &= ~GPIO_CRL_CNF3_0; NVIC_EnableIRQ(EXTI3_IRQn); EXTI->IMR |= EXTI_IMR_MR3; EXTI->RTSR |= EXTI_RTSR_TR3; EXTI->FTSR &= ~EXTI_FTSR_TR3;
}

Настройка завершена. Наша цель – по нажатию кнопки менять состояние светодиода (выключать, если он был включен, и наоборот), который мы уже настраивали ранее. Теперь осталось написать код, который будет реагировать на нажатие. Конечно, мы можем проверять регистр IDR в главном цикле, но это не самое лучшее решение.

Гораздо лучше использовать для таких целей прерывания. Обратимся к документации (это делается для более глубокого понимания – эту информацию можно получить и из наименования макросов в библиотеке), к разделу «8 Interrupts and events» ⇒ «External interrupt/event controller (EXTI)». На странице 133 можно найти функциональную схему работы этого модуля.

Блок «Edge detect circuit» может реагировать как на передний фронт (импульса), так и на задний или на оба сразу. Это задается соответствующими битами в соответствующих регистрах. Всё в той же документации можно найти схему подключения ножек к модулю EXTI:

Как видно, 3-я ножка порта A подключена к EXTI3. Чтобы глобально разрешить прерывания с EXTI3, необходимо произвести работу с регистром ISER (контроллера NVIC). К счастью, в файле core_cm3.h уже имеется готовая функция:

__STATIC_INLINE void NVIC_EnableIRQ(IRQn_Type IRQn)
{ NVIC->ISER[((uint32_t)(IRQn) >> 5)] = (1 IMR |= EXTI_IMR_MR3;

Последующие два регистра отвечают за прерывание по переднему фронту и заднему соответственно. Мы будем реагировать на передний, поэтому следует настроить RTSR:

Так как мы работаем с 3-й ножкой, нас интересует бит TR3:

EXTI->RTSR |= EXTI_RTSR_TR3;

Настройка прерывания закончена, осталось написать обработчик, название которого мы также возьмем в startup_.s файле.

Последний значимый для нас регистр именуется PR (англ. pending register). Дело в том, что прерывание может прийти от разных портов (что было видно на схеме выше), поэтому вам самостоятельно нужно проверять, с какого именно порта оно пришло, и сбрасывать «флаг», который записывается в соответствующий бит регистра PR.

Таким образом, код для обработчика примет следующий вид:

static volatile uint8_t flag = 0;
// …
void EXTI3_IRQHandler() { if (flag) { GPIOB->ODR |= GPIO_ODR_ODR0; flag = 0; } else { GPIOB->ODR &= ~GPIO_ODR_ODR0; flag = 1; } EXTI->PR |= EXTI_PR_PR3;
}

В стандартную библиотеку входят два файла для работы с модулем EXTI. Опять же, для повышения уровня абстракции мы предлагаем вам переписать работу с EXTI на функции стандартной библиотеки. В дальнейшем мы не будем рассматривать периферию так же подробно (на уровне регистров). Нашей целью было показать принцип – мы этой цели достигли.

Код урока можно найти на github.

Источник: http://stm32.chrns.com/post/149165794649/button

Обработка четырех кнопок на ATmega8

Мне в одном проекте понадобилось выполнять проверку нажатия сразу четырех кнопок.
Обычно если кнопок максимум 2 я применяю внешние прерывания, очень удобно и быстро можно организовать обработку их нажатия. Но когда кнопок больше, приходится хитрить.

В данном коде реализован опрос 4х кнопок, при желании можно увеличить их количество до 8(если использовать все вывода порта С). Принцип работы программы:

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

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

После чего переменная принимает одно из 4х значений, проверка и сверка нажатой кнопки происходит в программе ifstatus();

Код программы представлен для микроконтроллера ATmega8

  1. #include //Библиотека ввода/вывода

  2. #include //Библиотека прерываний

  3. unsigned char status=0;

  4. void pause (unsigned int a);

  5. void init_timer (void);

  6. void USART_Init( unsigned int ubrr);

  7. void USART_Transmit( unsigned char data );

  8. //Обработчик прерываний по срабатываю таймера 0

  9. ISR (TIMER0_OVF_vect)

  10. {

  11. if ((PINC&0x01)==0x00)

  12. {

  13. //Обработка нажатия кнопки

  14. pause(1000); //Пауза на 0.01С

  15. if ((PINC&0x01)==0x00) //Если нажата кнопка

  16. {

  17. while ((PINC&0x01)==0x00); //Пока нажата кнопка

  18. if (status==0)

  19. status=1; //Статус становится 1

  20. }

  21. }

  22. else

  23. if ((PINC&0x02)==0x00)

  24. {

  25. pause(1000); //Пауза на 0.01С

  26. if ((PINC&0x02)==0x00) //Если нажата кнопка

  27. {

  28. while ((PINC&0x02)==0x00); //Пока нажата кнопка

  29. if (status==0)

  30. status=2; //Статус становится 1

  31. }

  32. }

  33. else

  34. if ((PINC&0x04)==0x00)

  35. {

  36. pause(1000);

  37. if ((PINC&0x04)==0x00)

  38. {

  39. while ((PINC&0x04)==0x00);

  40. if (status==0)

  41. status=3;

  42. }

  43. }

  44. else

  45. if ((PINC&0x08)==0x00)

  46. {

  47. pause(1000);

  48. if ((PINC&0x08)==0x00)

  49. {

  50. while ((PINC&0x08)==0x00);

  51. if (status==0)

  52. status=4;

  53. }

  54. }

  55. else

  56. status=0; //В другом случаи ни одна кнопка не нажата

  57. TCNT0=0x00; //Обнуляем таймер счетчик 0

  58. TIFR=0x00; //Продолжаем ожидать нажатия

  59. return;

  60. }

  61. //Программа задержки

  62. void pause (unsigned int a)

  63. {

  64. unsigned int i;

  65. for (i=a;i>0;i–);

  66. }

  67. //Программа иницализации Таймера 0 0

  68. void init_timer (void)

  69. {

  70. TIMSK=(1

Источник: https://avrlab.com/node/364

Подключаем кнопки к AVR. Шаг №12

Обновлено 14.05.15. Здравствуйте дорогие друзья. В этой статье пойдет речь о кнопках и методе борьбы с дребезгом. В прошлой статье я рассказывал о первом своем устройстве —  анализаторе, где были применены кнопки, поэтому пора рассмотреть тонкости их работы.

Материала в интернете куча, но каждый индивидуальный взгляд на решение вопроса увеличивает скорость понимания данного направления. Поэтому я и решил написать. Ну что ж перейдем к кнопкам. Зачем нужны кнопки? К примеру вы создаете тот же барьер — реле напряжения, но для гибкости устройства необходимо менять граничные уровни напряжения, т.к.

сеть у каждого очень индивидуальна, к сожалению. Вот тут и приходят на помощь кнопки.

Кнопка —это механическое устройство для передачи сигнала/ввода информации путём замыкания или размыкания двух или более контактов. По сути своей является датчиком внешнего физического воздействия.

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

В основном применяют не фиксирующиеся нормально разомкнутые, но это решение строго личное , что и как использовать. Я дальше буду писать про нормально разомкнутые. Подключить кнопку к микроконтроллеру AVR, очень просто на рисунке ниже представлена схема подключения.

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

регистр DDRx выставляем в 0, далее подтягиваем внутренние резисторы, т.е. выставляет регистр PORTx в1.

А далее алгоритм опроса, вот здесь он может быть очень индивидуален, ну во первых если нам необходимо  просто ввести данные вначале программы и далее кнопки недолжны реагировать, то тут можно поместить опрос в циклическое условие, например ниже представлен кусочек кода моей программы на СИ для двух кнопок. 

while ((e == 0)&&(e1 == 0)) //  Начало цикла, берем любые переменные, к примеру равные нулю

        // Далее начинается обработка кода, пока не поменяется, хотя бы одно значение переменной на истинно

         {

   if ((PIND & 0×20) == 0) // здесь мы нажали на кнопку, и в данном случае на пине 5 порта установился 0. Ниже небольшой код по обработке дребезга контактов, о нем написал чуть ниже

   {

                   _delay_ms (50);         // Устранение дребезга клавиш

                   if ((PIND & 0×20) == 0) // Опять проверяем нажатие

                      e++;             //Увеличиваем на 1

                      ……     ;          // Здесь выполняется необходимая нам команда

                   while ((PIND & 0×20) == 0) //Все ждем отпускание кнопки

                          {}

}  // Выходим из условия

  else {}; //можно и неиспользовать, пустая комманда, даже занимающая сокото там тактов

              if ((PIND & 0×40) == 0) // а здесь мы нажал на кнопку с2, ну и т.д.

               {

                      _delay_ms (50);

                       if ((PIND & 0×40) == 0)

                      e1++;

                      ……;

                      while ((PIND & 0×40) == 0)

                      {}

               }

               else {};

             ……;

 }; // выходим из циклического условия если мы нажали хотя бы одну кнопку

Данный код представляет примерный подход опроса двух кнопок. Алгоритм может меняться как пожелает фантазия. Но самое главное что в коде опроса должен присутствовать антидребезговый код.

Что это? Дребезг это явление возникает в переключателях, которое представляет собой в момент переключения случайные многократные неконтролируемые замыкания и размыкания контактов и длятся они от десятков до сотен миллисекунд. На рисунке ниже у нас пример дребезга. Т.к.

в протеусе все идеально, то пришлось дорисовать реальность – дребезг красным. Что у нас получается, МК посчитает, перед переходом на другую команду в момент переключения кнопки любое состояние из полосы неопределенности 0 или 1 которая возникает при  колебаниях за счет дребезга контактов. Т.е.

выходит угадал не угадал… Техника требует точности. Борются двумя методами, первый для компенсации дребезга применяют переключатели с обратной связью, с характеристикой в виде прямоугольной петли гистерезиса и т. д., второй программный метод. Этот метод мы  и рассмотрим . В коде выше есть комментарий //Устранение дребезга клавиш.

к строке _delay_ms (50); я взял 50 мс, но для пущей уверенности можно и сто а вообще надо подстраиваться под свою разработку. В коде после задержки, когда колебания затухли, опять сравнивается состояние. Вот таким нехитрым методом происходит опрос кнопок.

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

Читайте даташит. Порт также настраиваем на вход, подтягиваем резистор. В нормальном состоянии, когда кнопка разомкнута на выводе присутствует 1. В момент замыкания на вывод приходит 0, где по спадающему фронту (настройка) запускается обработчик прерываний. Для запуска внешнего прерывания необходимо флаг I регистра SREG установить в 1.

При возникновения прерывания этот флаг аппаратно сбрасывается. Если мы хотим вызывать вложенные прерывания то необходимо программно установить этот флаг. При выходе из обработчика необходимо выполнить комманду reti; , которая установит этот флаг в 1.

Например в программе считывания кнопок на АЦП был описан способ считывания кнопок в прерывании, только уже используя АЦП.

Ниже приведен код для работы с внешним прерыванием.

GICR =0×40; //Управляющий регистр для разрешения/запрещения  прерываний. Разрешаем внешние прерывание INT0.
MCUCR = 0×02; //Конфигурационный регистр для выбора условий генераций внешнего прерывания. По спадающему фронту.
sei (); //установка флага I регистра SREG в 1. Разрешение общего прерывания.

Должен быть обязательно установлен.
SIGNAL (SIG_INTERRUPT0)//Обработчик прерывания по вектору внешнего прерывания. Данные векторы хорошо описаны в Шпаке для среды WinAVR.

{  //Здесь  пишем необходимый нам код при нажатии на кнопку   }
//Далее обработчик прерывания заканчивает свою работу, и программа возвращается в основной цикл.

Также наверное стоит упомянуть о регистре GIFR, флаги которого используются для индикации прерываний. Кому необходимо читаем даташит.Способов использования прерываний для кнопок куча, поэтому читаем, подбираем… На этом все. В следующей статье поговорим об использовании памяти EEPROM. Всем пока.

Просмотрено 30766 раз.

Источник: http://www.ap-impulse.ru/podklyuchaem-knopki-k-avr-shag-12/

Дребезг контактов кнопки или вред прерываний – INT0 в PIC18 | PRO-диод

30.04.2015 | Рубрика: PIC – микроконтроллеры

Цикл статей – PIC начинающим или основы основ

INT0 в PIC18 — про внешние прерывания

В этой статье наглядно покажем к чему приводит дребезг контактов, почему обрабатывая кнопки нужно учитывать этот эффект, а заодно разберемся с внешними прерываниями PIC18 на примере INT0.

Внешние прерывания (External interrupt) в PIC18

Помните, в прошлой статье я рассказал о прерываниях таймера TMR1? На схеме используемой демоплаты видно, что кнопка SW1 подключена к порту RB0. Порт RB0 примечателен тем, что на него выведена замечательная альтернативная функция — внешнее прерывание INT0.

PIC18F45К20 — распиновка

Если требуется, чтобы микроконтроллер сразу же обрабатывал внешние воздействия в виде перепадов и 1 на входах RB0, RB1, RB2, необходимо воспользоваться прерываниями INT0, INT1, INT2 соответственно. Каждое из прерываний INT можно настроить независимо друг от друга. Поскольку в нашей демоплатке кнопка подключена на RB0, то рассматривать буду прерывание INT0.

Прерывание INT происходит при изменении логического уровня входного сигнала. INT можно настроить так, чтобы событие прерывания происходило либо по фронту, т.е. сигнал меняется с лог. 0 на лог. 1, либо наоборот — по спаду, т.е. сигнал на меняется с лог. 1 на лог. 0. Эту настройку можно делать в любой момент программы битом INTEDG0 в регистре INTCON2.

Биты регистра INTCON2

INTCON2bits.INTEDG0 = 1; // Прерывание по фронту INTCON2bits.INTEDG0 = 0; // Прерывание по спаду

Возникшее прерывание необходимо сбросить:

INTCONbits.INT0IF = 0; // Сбросили флаг прерывания по INT0

Если этого не сделать, прерывание будет выполняться в бесконечном цикле и программа зависнет.

Кнопка SW1 подключена так, что при нажатии сигнал на RB0 изменяется из состояния 1 в состояние 0, при отпускании из 0 в 1.

Изменение логического уровня в зависимости от состояния кнопки

Теперь можно дополнить комментарии в приведенном выше коде:

INTCON2bits.INTEDG0 = 1; // Прерывание по фронту (при отпускании кнопки) INTCON2bits.INTEDG0 = 0; // Прерывание по спаду (при нажатии на кнопку)

Для наглядности срабатывания кнопки напишем программу с эффектом двоичный счетчик, в которой счет прибавляется при нажатии на кнопку. Код файла main_intv2.c:

Источник: https://pro-diod.ru/programms/pic-micro/drebezg-kontaktov-knopki-ili-vred-preryvanij-int0-v-pic18.html

Прерывания Arduino с помощью attachInterrupt

Прерывания — очень важный механизм Arduino, позволяющий внешним устройствам взаимодействовать с контроллером при возникновении разных событий.

Установив обработчик аппаратных прерываний в скетче, мы сможем реагировать на включение или выключение кнопки, нажатие клавиатуры, мышки, тики таймера RTC, получение новых данных по UART, I2C или SPI.

В этой статье мы узнаем, как работают прерывания на платах Ардуино Uno, Mega или Nano и приведем пример  использования функции Arduino attachInterrupt().

Прерывания в Ардуино

Прерывание — это сигнал, который сообщает процессору о наступлении какого-либо события, которое требует незамедлительного внимания.

Процессор должен отреагировать на этот сигнал, прервав выполнение текущих инструкций и передав управление обработчику прерывания (ISR, Interrupt Service Routine).

Обработчик — это обычная функция, которую мы пишем сами и помещаем туда тот код, который должен отреагировать на событие.

После обслуживания прерывания ISR функция завершает свою работу и процессор с удовольствием возвращается к прерванным занятиям — продолжает  выполнять код с того места, в котором остановился.

Все это происходит автоматически, поэтому наша задача заключается только в том, чтобы написать обработчик прерывания, ничего при этом не сломав и не заставляя процессор слишком часто отвлекаться на нас.

Понадобится понимание схемы, принципов работы подключаемых устройств и представление о том, как часто может вызываться прерывание, каковы особенности его возникновения. Все это и составляет основную сложность работы с прерываниями.

Аппаратные и программные прерывания

Прерывания в Ардуино можно разделить на несколько видов:

  • Аппаратные прерывания. Прерывание на уровне микропроцессорной архитектуры. Самое событие может произойти в производительный момент от внешнего устройства – например, нажатие кнопки на клавиатуре, движение компьютерной мыши и т.п.
  • Программные прерывания. Запускаются внутри программы с помощью специальной инструкции. Используются для того, чтобы вызвать обработчик прерываний.
  • Внутренние (синхронные) прерывания. Внутреннее прерывание возникает в результате изменения или нарушения в исполнении программы (например, при обращении к недопустимому адресу, недопустимый код операции и другие).

Зачем нужны аппаратные прерывания

Аппаратные прерывания возникают в ответ на внешнее событие и исходят от внешнего аппаратного устройства. В Ардуино представлены 4 типа аппаратных прерываний. Все они различаются сигналом на контакте прерывания:

  • Контакт притянут к земле. Обработчик прерывания исполняется до тех пор, пока на пине прерывания будет сигнал LOW.
  • Изменение сигнала на контакте. В таком случае Ардуино выполняет обработчик прерывания, когда на пине прерывания происходит изменение сигнала.
  • Изменение сигнала от LOW к HIGH на контакте — при изменении с низкого сигнала на высокий будет исполняться обработчик прерывания.
  • Изменение сигнала от HIGH к LOW на контакте – при изменении с высокого сигнала на низкий будет исполняться обработчик прерывания.

Прерывания полезны в программах Ардуино, так как помогают решать проблемы синхронизации. Например, при работе с UART прерывания позволяют не отслеживать поступление каждого символа.

Внешнее аппаратное устройство подает сигнал прерывания, процессор сразу же вызывает обработчик прерывания, который вовремя захватывает символ.

Это позволяет экономить процессорное время, которое без прерываний тратилось бы на проверку статуса UART, вместо этого все необходимые действия выполняются обработчиком прерывания, не затрагивая главную программу. Особых возможностей от аппаратного устройства не требуется.

Основными причинами, по которым необходимо вызвать прерывание, являются:

  • Определение изменения состояния вывода;
  • Прерывание по таймеру;
  • Прерывания данных по SPI, I2C, USART;
  • Аналогово-цифровое преобразование;
  • Готовность использовать EEPROM, флеш-память.

Как реализуются прерывания в Ардуино

При поступлении сигнала прерывания работа в цикле loop() приостанавливается. Начинается выполнение функции, которая объявляется на выполнение при прерывании.

Объявленная функция не может принимать входные значения и возвращать значения при завершении работы. На сам код в основном цикле программы прерывание не влияет.

Для работы с прерываниями в Ардуино используется стандартная функция attachInterrupt().

Отличие реализации прерываний в разных платах Ардуино

В зависимости от аппаратной реализации конкретной модели микроконтроллера есть несколько прерываний. Плата Arduino Uno имеет 2 прерывания на втором и третьем пине, но если требуется более двух выходов, плата поддерживает специальный режим «pin-change».

Этот режим работает по изменению входа для всех пинов. Отличие режима прерывания по изменению входа заключается в том, что прерывания могут генерироваться на любом из восьми контактов.

Обработка в таком случае будет сложнее и дольше, так как придется отслеживать последнее состояние на каждом из контактов.

На других платах число прерываний выше. Например, плата Ардуино Мега 2560 имеет 6 пинов, которые могут обрабатывать внешние прерывания. Для всех плат Ардуино при работе с функцией attachInterrupt (interrupt, function, mode) аргумент Inerrupt 0 связан с цифровым пином 2.

Прерывания в языке Arduino

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

Синтаксис attachInterrupt()

Функция attachInterrupt используется для работы с прерываниями. Она служит для соединения внешнего прерывания с обработчиком.

Синтаксис вызова: attachInterrupt(interrupt, function, mode)

Аргументы функции:

  • interrupt – номер вызываемого прерывания (стандартно 0 – для 2-го пина, для платы Ардуино Уно 1 – для 3-го пина),
  • function – название вызываемой функции при прерывании(важно – функция не должна ни принимать, ни возвращать какие-либо значения),
  • mode – условие срабатывания прерывания.

Возможна установка следующих вариантов условий срабатывания:

  • LOW – выполняется по низкому уровню сигнала, когда на контакте нулевое значение. Прерывание может циклично повторяться — например, при нажатой кнопке.
  • CHANGE – по фронту, прерывание происходит при изменении сигнала с высокого на низкий или наоборот. Выполняется один раз при любой смене сигнала.
  • RISING – выполнение прерывания один раз при изменении сигнала от LOW к HIGH.
  • FALLING – выполнение прерывания один раз при изменении сигнала от HIGH к LOW.4

Важные замечания

При работе с прерываниями нужно обязательно учитывать следующие важные ограничения:

  • Функция — обработчик не должна выполняться слишком долго. Все дело в том, что Ардуино не может обрабатывать несколько прерываний одновременно. Пока выполняется ваша функция-обработчик, все остальные прерывания останутся без внимания и вы можете пропустить важные события. Если надо делать что-то большое — просто передавайте обработку событий в основном цикле loop(). В обработчике вы можете лишь устанавливать флаг события, а в loop — проверять флаг и обрабатывать его.
  • Нужно быть очень аккуратными с переменными. Интеллектуальный компилятор C++ может «пере оптимизировать» вашу программу — убрать не нужные, на его взгляд, переменные. Компилятор просто не увидит, что вы устанавливаете какие-то переменные в одной части, а используете — в другой. Для устранения такой вероятности в случае с базовыми типами данных можно использовать ключевое слово volatile, например так: volatile boolean state = 0. Но этот метод не сработает со сложными структурами данных. Так что надо быть всегда на чеку.
  • Не рекомендуется использовать большое количество прерываний (старайтесь не использовать более 6-8). Большое количество разнообразных событий требует серьезного усложнения кода, а, значит,   ведет к ошибкам. К тому же надо понимать, что ни о какой временной точности исполнения в системах с большим количеством прерываний речи быть не может — вы никогда точно не поймете, каков промежуток между вызовами важных для вас команд.
  • В обработчиках категорически нельзя использовать delay(). Механизм определения интервала задержки использует таймеры, а они тоже работают на прерываниях, которые заблокирует ваш обработчик. В итоге все будут ждать всех и программа зависнет. По этой же причине нельзя использовать протоколы связи, основанные на прерываниях (например, i2c).

Примеры использования attachInterrupt

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

#define PIN_LED 13 volatile boolean actionState = LOW;
void setup() { pinMode(PIN_LED, OUTPUT); // Устанавливаем прерывание // Функция myEventListener вызовется тогда, когда // на 2 пине (прерываниие 0 связано с пином 2) // изменится сигнал (не важно, в какую сторону) attachInterrupt(0, myEventListener, CHANGE); } void loop() { // В функции loop мы ничего не делаем, т.к. весь код обработки событий будет в функции myEventListener
} void myEventListener() { actionState != actionState; // // Выполняем другие действия, например, включаем или выключаем светодиод digitalWrite(PIN_LED, actionState);
}

Давайте рассмотрим несколько примеров более сложных прерываний и их обработчиков: для таймера и кнопок.

Прерывания по нажатию кнопки с антидребезгом

При прерывании по нажатию кнопки возникает проблема дребезга – перед тем, как контакты плотно соприкоснутся при нажатии кнопки, они будут колебаться, порождая несколько срабатываний. Бороться с дребезгом можно двумя способами – аппаратно, то есть, припаивая к кнопке конденсатора, и программно.

Избавиться от дребезга можно при помощи функции millis – она позволяет засечь время, прошедшее от первого срабатывания кнопки.

if(digitalRead(2)==HIGH) { //при нажатии кнопки
//Если от предыдущего нажатия прошло больше 100 миллисекунд
if (millis() – previousMillis >= 100) {
//Запоминается время первого срабатывания
previousMillis = millis(); if (led==oldled) { //происходит проверка того, что состояние кнопки не изменилось
led=!led;
}

Этот код позволяет удалить дребезг и не блокирует исполнение программы, как в случае с функцией delay, которая недопустима в прерываниях.

Прерывания по таймеру

Таймером называется счетчик, который производит счет с некоторой частотой, получаемой из процессорных 16 МГц. Можно произвести конфигурацию делителя частоты для получения нужного режима счета. Также можно настроить счетчик для генерации прерываний  при достижении заданного значения.

Таймер и прерывание по таймеру позволяет выполнять прерывание один раз в миллисекунду. В Ардуино имеется 3 таймера — Timer0, Timer1 и Timer2.

Timer0 используется для генерации прерываний один раз в миллисекунду, при этом происходит обновление счетчика, который передается в функцию millis (). Этот таймер является восьмибитным и считает от 0 до 255.

Прерывание генерируется при достижении значения 255. По умолчанию используется тактовый делитель на 65, чтобы получить частоту, близкую к 1 кГц.

Для сравнения состояния на таймере и сохраненных данных используются регистры сравнения. В данном примере код будет генерировать прерывание при достижении значения 0xAF на счетчике.

OCR0A = 0xAF;

TIMSK0 |= _BV(OCIE0A);

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

Несколько векторов прерывания объединяются в таблицу векторов прерываний.  Таймер в данном случае будет иметь название TIMER0_COMPA_vect.

В этом обработчике будут производиться те же действия, что и в loop ().

SIGNAL(TIMER0_COMPA_vect)
{
unsigned long currentMillis = millis(); sweeper1.Update(currentMillis); if(digitalRead(2) == HIGH)
{
sweeper2.Update(currentMillis);
led1.Update(currentMillis);
} led2.Update(currentMillis);
led3.Update(currentMillis); } //Функция loop () останется пустой. void loop() { }

Подведение итогов

Прерывание в Ардуино — довольно сложная тема, потому что приходится думать сразу обо всей архитектуре проекта, представлять как выполняется код, какие возможны события, что происходит, когда основной код прерывается.

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

Источник: https://ArduinoMaster.ru/program/preryvaniya-arduino-attachinterrupt/

STM32. Прерывания

В этой статье мы поделимся базовыми знаниями о практической реализации механизма прерываний от различных источников в этих микроконтроллерах. В частности будут рассмотрены прерывания от кнопки и по таймеру, а также создание внешних переменных. Напомню, что наш курс сквозной и мы рассчитываем при написании этой статьи, что читатель ознакомился с предыдущими статьями.

Внешние прерывания

Ранее мы рассматривали подключение кнопки к выводу контроллера и опрос ее состояния в основном цикле. Такая реализация часто встречается на практике, но в некоторых случаях удобнее использовать прерывания.
Для начала создадим проект в CubeMX.

Необходимо запустить его и выбрать используемую вами отладочную плату. В нашем случае это Nucleo64 на контроллере STM32F030R8T6. Подготовка проекта была описана в предыдущих статьях.

Если посмотреть в параметрах преднастроенного проекта, то можно увидеть, что голубая кнопка, подключенная к выводу PC13, уже настроена в режиме прерываний.

Чтобы это прерывание начало срабатывать, необходимо включить его. Для этого перейдите во вкладку “Configuration” и в меню “NVIC” установите галочку “EXTI line 4 to 15 interrupts”. Обратите внимание, что это прерывание генерируется одно на несколько каналов.

Это означает, что если надо подключить несколько кнопок, то и в обработчике прерывания потом придется опрашивать эти кнопки, чтобы выяснить какая из них конкретно была нажата.Также обратите внимание, что в пункте меню “GPIO” можно установить режим работы прерывания.

По умолчанию установлено “External Interrupt Mode with Falling edge trigger detection”, то есть прерывание генерируется по спаду.На этом предварительная настройка окончена. Можно сгенерировать проект и приступить к написанию кода.

После запуска Keil uVision в папке “Application/User” необходимо найти файл “stm32f0xx_it.c”.

Это файл в котором уже сгенерированы обработчики различных прерываний. Нас интересует обработчик EXTI4_15_IRQHandler.

В обработчике этого прерывания мы вызовем функцию HAL_GPIO_TogglePin(). Она изменяет состояние вывода на противоположное. На плате Nucleo есть светодиод, подключенный к выводу PA5. Если мы скомпилируем и загрузим этот проект, то кнопка начнет переключать состояние светодиода на противоположное.

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

Прерывания от таймера

Ранее мы рассматривали как организовать мигание светодиоды в основном цикле. Мы делали это самым простым способом, основная проблема которого в том, что программа почти все время остановлена вызовом функции HAL_Delay(). С другой стороны в микроконтроллерах есть таймеры, которые могут самостоятельно генерировать прерывания и выполнять какие-то действия.

При этом основная программа будет останавливаться только на время, необходимое для выполнения команд обработчика прерываний. Мы рассмотрим самый простой пример работы таймера, когда он генерирует прерывания через равные интервалы времени. В первую очередь требуется включить таймер. Для этого в CubeMX во вкладке “Pinout” включите таймер.

Для примера мы включили 14ый таймер.

https://www.youtube.com/watch?v=EptTr2nUCJk

Во вкладке “Clock Configuration” можно изменить или просто проверить тактовую частоту таймера. У нас установлена тактовая частота 48МГц.Затем переходим на вкладку “Configuration” и в меню “TIM14” настраиваем таймер. Делитель “Prescaler (PSC — 16 bits value)” мы установили на 47999.

То есть тактовая частота таймера 48МГц будет делиться в 48000 раз (так как счет идет от нуля) и прибавление внутреннего счетчика таймера будет происходить раз в одну миллисекунду. В пункте “Counter Period” устанавливается значение счетчика, при котором генерируется прерывание и счет начинается заново.

Мы хотим, чтобы светодиод изменял свое состояние раз в секунду, поэтому установили значение 999. Таймер будет с периодичностью в 1мс считать от 0 до 999, то есть прерывание будет вызываться каждую тысячу миллисекунд.Также на вкладке таймера “NVIC Settings” надо установить галочку, разрешающую глобальные прерывания от таймера.

Обратите внимание, что в сводной таблице таймеров “NVIC” также появилась новая строка с таймером и прерывания от него включены.
Генерируем проект заново и открываем файл main.c. В нем нам потребуется в ручную запустить таймер в нужном нам режиме. Делается это командой HAL_TIM_Base_Start_IT(&htim14);. IT подразумевает запуск таймера в режиме прерываний.

Указатель &htim14 передает функции структуру данных, в которой описаны настройки таймеров. Она генерируется автоматически CubeMX’ом.
Обратите внимание, что функция HAL_TIM_Base_Start_IT(&htim14); вызывается до вхождения в бесконечный цикл.

Далее снова переходим в файл “stm32f0xx_it.

c” и в обработчике прерывания TIM14_IRQHandler добавляем код, изменяющий состояние вывода.

После компиляции и загрузки светодиод начнет мигать раз в секунду. И снова, благодаря использованию прерываний, у нас полностью разгружен основной цикл и светодиод мигает в фоновом процессе.

Внешние переменные

На практике очень часто в прерывании возникает необходимость изменять переменные, которые используются в основном цикле. То есть обращаться к переменным, описанным в другом файле. Для примера запустим мигание светодиодом в основном цикле с переменной частотой, а частоту будем изменять в прерывании от кнопки. Начнем с создания переменной в файле main.c.

Далее закомментируем функцию запуска таймера в 96ой строке и добавим код, мигающий светодиодом:Если мы сейчас скомпилируем и загрузим эту программу, то светодиод начнет мигать с периодичностью 200мс. Но мы хотим изменять эту периодичность при помощи кнопки.

Поэтому переходим в файл stm32f0xx_it.c и создаем в нем внешнюю переменную extern uint16_t pause;. Служебное слово extern расширяет область видимости переменной. Важно то, что переменная инициализируется, но не определяется.

То есть ей не присваивается никакое значение.

Осталось только добавить изменение этой переменной в прерывании. В строках 103, 104 мы просто прибавляем 100, если переменная меньше тысячи и присваиваем значение, если она равна тысяче.
Скомпилированная программа при старте будет мигать светодиодом быстро, а при нажатии кнопки будет изменять частоту мигания светодиодом.

Мы разобрали самые простые варианты прерываний, но даже они очень эффективные и будут использоваться нами в будущих статьях. Ссылки на остальные статьи цикла можно найти здесь.
Мы будем очень рады, если вы поддержите наш ресурс и посетите магазин наших товаров shop.customelectronics.ru.

Источник: http://www.customelectronics.ru/stm32-preryivaniya/

Чтение состояния 10 и более кнопок с помощью двух выводов микроконтроллера

» Схемы » Применение микроконтроллеров

11-03-2014

Журнал РАДИОЛОЦМАН, май 2013

Aruna Rubasinghe

EDN

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

Например, используя аналоговый вход микроконтроллера, можно определять нажатую кнопку по уровню напряжения, присвоенного ей посредством цепочки резисторов.

Можно также использовать приборы с однопроводным интерфейсом, такие, скажем, как 8-канальный адресуемый ключ Maxim DS2408.

Первому способу присущ ряд недостатков: микроконтроллер должен иметь встроенный АЦП, время подавления дребезга снижает частоту сканирования копок, а размыкание кнопки во время выполнения выборки АЦП приводит к ошибкам. Второй способ, использующий интерфейс 1-Wire, отличается относительно низкой скоростью и требует постоянного опроса микросхемы DS2408, в ответ на каждое обращение возвращающей 8-разрядный код позиции кнопки.

 Используя в проектах большое количество кнопок, постоянно сталкиваюсь с проблемой нехватки количества внешних прерываний. Вроде выбрал подходящий по нафаршированности камень, есть все что надо – АЦП, 2 юарта, много флэша, а вот прерываний нехватает…

 Или чаще наоборот, небольшой проект, на который с лихвой хватит ресурсов tiny2313, и нужно обрабатывать много внешних событий (таких как нажатие на кнопки). Брать камень крупнее не спортивно, да и жалко когда ресурсы будут низачто простаивать :), поэтому был найден простой способ как, использовав всего один вход прерывания, обработать практически неограниченное количество кнопок.

 Суть метода в том, чтобы одним прерыванием фиксировать нажатие любой кнопки. А уже в обработчике прерывания просканировать и узнать какая именно кнопка была нажата.

Для примера вот небольшая схемка:

Диоды D1-D4 предназначены для того чтобы разделить кнопки между собой. Резисторы можно ставить номиналом по 4,7-10 кОм.

и тестовая программка для микроконтроллера

$regfile = “attiny2313.dat”
$crystal = 1000000
$baud = 1200

Dim W As Byte              'переменная с номером нажатой кнопки

'кнопки
Config Portb.0 = Input
Config Portb.

1 = Input
Config Portb.2 = Input
Config Portb.3 = Input

Config Portd.6 = Output    'сюда подключается светодиод индикации
Portd.

6 = 0

Config Int0 = Falling      'прерывание по нисходящему фронту
On Int0 Button
Enable Interrupts          'разрешаем прерывания
Enable Int0

'Основной цикл программы
Do

Print W                    'печатаем номер нажатой кнопки
W = 0
Idle                       'и засыпаем

Loop

End

'обработчик прерывания
Button:
'здесь мы в цикле сканируем все подключенные кнопки
Do
 If Pinb.3 = 0 Then
  W = 1
 End If

 If Pinb.2 = 0 Then
  W = 2
 End If

 If Pinb.1 = 0 Then
  W = 3
 End If

 If Pinb.0 = 0 Then
  W = 4
 End If
Pulseout Portd , 6 , 2500  'мигнем светодиодом
Loop Until W  0          'если дребезг помешал вычислению нажатой кнопки, повторяем

Waitms 10
Gifr = 64                  'сброс флага прерывания

Return

 
 Светодиод на схеме не показан, из программы думаю ясно, что он подключается к ножке PortD.6

 В программе организован спящий режим микроконтроллера, из которого его можно вывести прижав вывод INT0 к земле. Это делается нажатием любой кнопки.

В обработчике прерывания происходит сканирование портов МК к которым подключены кнопки и если на одном из них ноль – значит нажата эта самая кнопка.

Дальше присваиваем некой переменной значение соответствующее нажатой кнопки, мигаем светодиодом и уходим в основной цикл, где печатаем номер кнопки и снова даем команду микроконтроллеру “уснуть”.

 Номер кнопки в примере печатается в терминал на скорости 1200 бод.

 Как видно это очень простой способ, который подойдет под различные задачи. А поменяв немного алгоритм сканирования портов, можно организовать обработку одновременно нескольких кнопок. 

Тестовый код

Рисунок 1. Подключение к микроконтроллеру 10 кнопок с помощью двух линий ввода/вывода. Количество кнопок может быть увеличено при каскадировании нескольких микросхем CD4017 через выходы переноса.

В статье описан еще один способ считывания статуса множества кнопок или переключателей, для которого потребуются лишь две цифровые линии ввода/вывода микроконтроллера и прерывания от интегрированного в микроконтроллер таймера (Рисунок 1).

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

Можно также применить схему, изображенную на Рисунке 2, сохранив преимущество двухпроводного варианта. Диоды защищают выходы счетчика от повреждения при одновременном нажатии нескольких кнопок.

Каскадированием нескольких микросхем CD4017 можно увеличить количество подключаемых кнопок, используя выход переноса (вывод 12) и вход счетных импульсов (вывод 14).

Рисунок 2. Добавив три резистора и транзистор, можно защитить схему от сбоев синхронизации, не используя третью линию ввода/вывода микроконтроллера.

Надежность работы схемы после первоначального включения и инициализации микроконтроллера зависит от последующей синхронизации счетчика CD4017 с таймером-счетчиком микроконтроллера.

Однако синхронизация может быть нарушена электростатическими разрядами или электромагнитными помехами, например, от находящегося поблизости мобильного телефона, поэтому целесообразно предусмотреть в коде программы микроконтроллера функцию периодического аппаратного сброса счетчика CD4017.

На Рисунке 2 показано, как можно сделать это без использования третьей линии ввода/вывода микроконтроллера.

Такая возможность обеспечивается наличием трех состояний выхода микроконтроллера: высокого, низкого и высокоимпедансного состояния (Z-состояние), когда вывод порта временно переключается в режим ввода.

При «лог. 1» транзистор Q1 включается через резистор R4, устанавливая высокий логический уровень напряжения в точке V1 и напряжение ниже логического нуля в точке V2. Таким образом, на тактовом входе микросхемы устанавливается высокий логический уровень, а на входе сброса удерживается низкий.

В состоянии «лог. 0» транзистор Q1 выключается, устанавливая низкий логический уровень напряжения в точке V1 и напряжение выше логической единицы в точке V2. В этом случае на тактовом входе микросхемы устанавливается низкий логический уровень, а на входе сброса удерживается высокий.

Если же вход будет «оторван» вследствие переключения выхода микроконтроллера режим ввода, транзистор Q1 включится через резисторы R3 и R4, а напряжение в точках V1 и V2 опустится ниже порога логического нуля. При этом на счетном входе и входе сброса микросхемы устанавливается низкий уровень.

Таким образом, чтобы сформировать фронт счетного импульса, необходимо изменять состояния вывода микроконтроллера в следующей последовательности: Z-состояние > «лог. 1» > Z-состояние. Аналогично, для сброса счетчика CD4017 последовательность должна быть такой: Z-состояние > «лог. 0» > Z-состояние.

Блок-схема на Рисунке 3 иллюстрирует процесс считывания микроконтроллером состояния всех кнопок и используемые в программе микроконтроллера функции.

На этапе инициализации микроконтроллер сбрасывает переменную счетчика (n=0) и запускает таймер, сконфигурированный на генерацию прерываний по переполнению каждую 1 мс.

В обработчике прерываний от таймера выполняется следующие операции: запрещаются внешние прерывания, значение переменной инкрементируется на 1, на микросхему CD4017 подается синхроимпульс длительностью 10 мкс, разрешаются внешние прерывания.

Рисунок 3. Блок-схема программы микроконтроллера для чтения состояния всех кнопок. В каждый момент времени должна быть нажата только одна кнопка.

Ввиду того, что каждую миллисекунду микроконтроллер посылает импульс счетчику CD4017 и инкрементирует значение переменной собственного счетчика до тех пор, пока оно меньше 9, номер выхода CD4017, на котором появляется высокий логический уровень, будет соответствовать значению переменной счетчика микроконтроллера.

Например, если в счетчике микроконтроллера число 2, то высокий уровень появится на выходе 2 микросхемы CD4017 (вывод 4), в то время как на остальных выходах будет низкий логический уровень. Если в этот момент пользователь нажал кнопку S2, на вывод внешнего прерывания микроконтроллера поступит сигнал «лог. 1».

Нажатие других кнопок не приведет к формированию сигнала внешнего прерывания из-за низкого логического уровня на остальных выходах CD4017.

При поступлении сигнала внешнего прерывания микроконтроллер получает из памяти значение переменной счетчика (в нашем примере это 2), определяет нажатие кнопки S2 и выполняет соответствующие функции и операции. По достижении переменной значения 9 она программно сбрасывается в 0, так же как счетчик CD4017 сбрасывается автоматически на десятом импульсе.

Обратите внимание, что в каждый момент времени нажатой должна быть только одна кнопка. Если одновременно нажаты две последовательные кнопки, микроконтроллеру может не хватить времени между последовательными состояниями для регистрации фронта сигнала внешнего прерывания. Возможный вариант решения этой проблемы поясняет блок-схема на Рисунке 4.

Рисунок 4. При использовании кнопок с фиксацией для декодирования комбинаций состояний нескольких кнопок можно проверять состояния входа внешнего прерывания микроконтроллера.

Дополнительные материалы:

Многокнопочная клавиатура с интерфейсом 1-Wire

Источник: https://www.rlocman.ru/shem/schematics.html?di=147957

Ссылка на основную публикацию
Adblock
detector
",css:{backgroundColor:"#000",opacity:.6}},container:{block:void 0,tpl:"
"},wrap:void 0,body:void 0,errors:{tpl:"
",autoclose_delay:2e3,ajax_unsuccessful_load:"Error"},openEffect:{type:"fade",speed:400},closeEffect:{type:"fade",speed:400},beforeOpen:n.noop,afterOpen:n.noop,beforeClose:n.noop,afterClose:n.noop,afterLoading:n.noop,afterLoadingOnShow:n.noop,errorLoading:n.noop},o=0,p=n([]),h={isEventOut:function(a,b){var c=!0;return n(a).each(function(){n(b.target).get(0)==n(this).get(0)&&(c=!1),0==n(b.target).closest("HTML",n(this).get(0)).length&&(c=!1)}),c}},q={getParentEl:function(a){var b=n(a);return b.data("arcticmodal")?b:(b=n(a).closest(".arcticmodal-container").data("arcticmodalParentEl"),!!b&&b)},transition:function(a,b,c,d){switch(d=null==d?n.noop:d,c.type){case"fade":"show"==b?a.fadeIn(c.speed,d):a.fadeOut(c.speed,d);break;case"none":"show"==b?a.show():a.hide(),d();}},prepare_body:function(a,b){n(".arcticmodal-close",a.body).unbind("click.arcticmodal").bind("click.arcticmodal",function(){return b.arcticmodal("close"),!1})},init_el:function(d,a){var b=d.data("arcticmodal");if(!b){if(b=a,o++,b.modalID=o,b.overlay.block=n(b.overlay.tpl),b.overlay.block.css(b.overlay.css),b.container.block=n(b.container.tpl),b.body=n(".arcticmodal-container_i2",b.container.block),a.clone?b.body.html(d.clone(!0)):(d.before("
"),b.body.html(d)),q.prepare_body(b,d),b.closeOnOverlayClick&&b.overlay.block.add(b.container.block).click(function(a){h.isEventOut(n(">*",b.body),a)&&d.arcticmodal("close")}),b.container.block.data("arcticmodalParentEl",d),d.data("arcticmodal",b),p=n.merge(p,d),n.proxy(e.show,d)(),"html"==b.type)return d;if(null!=b.ajax.beforeSend){var c=b.ajax.beforeSend;delete b.ajax.beforeSend}if(null!=b.ajax.success){var f=b.ajax.success;delete b.ajax.success}if(null!=b.ajax.error){var g=b.ajax.error;delete b.ajax.error}var j=n.extend(!0,{url:b.url,beforeSend:function(){null==c?b.body.html("
"):c(b,d)},success:function(c){d.trigger("afterLoading"),b.afterLoading(b,d,c),null==f?b.body.html(c):f(b,d,c),q.prepare_body(b,d),d.trigger("afterLoadingOnShow"),b.afterLoadingOnShow(b,d,c)},error:function(){d.trigger("errorLoading"),b.errorLoading(b,d),null==g?(b.body.html(b.errors.tpl),n(".arcticmodal-error",b.body).html(b.errors.ajax_unsuccessful_load),n(".arcticmodal-close",b.body).click(function(){return d.arcticmodal("close"),!1}),b.errors.autoclose_delay&&setTimeout(function(){d.arcticmodal("close")},b.errors.autoclose_delay)):g(b,d)}},b.ajax);b.ajax_request=n.ajax(j),d.data("arcticmodal",b)}},init:function(b){if(b=n.extend(!0,{},a,b),!n.isFunction(this))return this.each(function(){q.init_el(n(this),n.extend(!0,{},b))});if(null==b)return void n.error("jquery.arcticmodal: Uncorrect parameters");if(""==b.type)return void n.error("jquery.arcticmodal: Don't set parameter \"type\"");switch(b.type){case"html":if(""==b.content)return void n.error("jquery.arcticmodal: Don't set parameter \"content\"");var e=b.content;return b.content="",q.init_el(n(e),b);case"ajax":return""==b.url?void n.error("jquery.arcticmodal: Don't set parameter \"url\""):q.init_el(n("
"),b);}}},e={show:function(){var a=q.getParentEl(this);if(!1===a)return void n.error("jquery.arcticmodal: Uncorrect call");var b=a.data("arcticmodal");if(b.overlay.block.hide(),b.container.block.hide(),n("BODY").append(b.overlay.block),n("BODY").append(b.container.block),b.beforeOpen(b,a),a.trigger("beforeOpen"),"hidden"!=b.wrap.css("overflow")){b.wrap.data("arcticmodalOverflow",b.wrap.css("overflow"));var c=b.wrap.outerWidth(!0);b.wrap.css("overflow","hidden");var d=b.wrap.outerWidth(!0);d!=c&&b.wrap.css("marginRight",d-c+"px")}return p.not(a).each(function(){var a=n(this).data("arcticmodal");a.overlay.block.hide()}),q.transition(b.overlay.block,"show",1*")),b.overlay.block.remove(),b.container.block.remove(),a.data("arcticmodal",null),n(".arcticmodal-container").length||(b.wrap.data("arcticmodalOverflow")&&b.wrap.css("overflow",b.wrap.data("arcticmodalOverflow")),b.wrap.css("marginRight",0))}),"ajax"==b.type&&b.ajax_request.abort(),p=p.not(a))})},setDefault:function(b){n.extend(!0,a,b)}};n(function(){a.wrap=n(document.all&&!document.querySelector?"html":"body")}),n(document).bind("keyup.arcticmodal",function(d){var a=p.last();if(a.length){var b=a.data("arcticmodal");b.closeOnEsc&&27===d.keyCode&&a.arcticmodal("close")}}),n.arcticmodal=n.fn.arcticmodal=function(a){return e[a]?e[a].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof a&&a?void n.error("jquery.arcticmodal: Method "+a+" does not exist"):q.init.apply(this,arguments)}}(jQuery)}var debugMode="undefined"!=typeof debugFlatPM&&debugFlatPM,duplicateMode="undefined"!=typeof duplicateFlatPM&&duplicateFlatPM,countMode="undefined"!=typeof countFlatPM&&countFlatPM;document["wri"+"te"]=function(a){let b=document.createElement("div");jQuery(document.currentScript).after(b),flatPM_setHTML(b,a),jQuery(b).contents().unwrap()};function flatPM_sticky(c,d,e=0){function f(){if(null==a){let b=getComputedStyle(g,""),c="";for(let a=0;a=b.top-h?b.top-h{const d=c.split("=");return d[0]===a?decodeURIComponent(d[1]):b},""),c=""==b?void 0:b;return c}function flatPM_testCookie(){let a="test_56445";try{return localStorage.setItem(a,a),localStorage.removeItem(a),!0}catch(a){return!1}}function flatPM_grep(a,b,c){return jQuery.grep(a,(a,d)=>c?d==b:0==(d+1)%b)}function flatPM_random(a,b){return Math.floor(Math.random()*(b-a+1))+a}