Курс arduino – прерывания, создание функций, советы

Прерывания 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/

Ардуино для начинающих. Урок 14. Прерывания

В этом уроке мы поговорим о прерываниях. Как понятно из названия, прерывание это событие, которое приостанавливает выполнение текущих задач и передает управление обработчику прерывания. Обработчик прерывания — это функция.

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

Если же использовать прерывание, то такой проблемы не возникнет, так как прерывания имеют более высокий приоритет.

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

Отличный набор для начинающих:
Arduino Uno:
Инфракрасный датчик расстояния:
Инвертирующий триггер шмитта:
Комплект конденсаторов 120 штук:

В Arduino имеется 4 вида аппаратных прерываний. Отличаются они сигналом на контакте прерывания.

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

Если прерывание ожидает нажатия кнопки, то это может стать проблемой из-за дребезга контактов. В 6 уроке мы уже говорили о дребезге контактов. Тогда мы использовали функцию delay(), но в прерываниях данная функция не доступна.

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

Подключается все по следующей схеме:

подключение кнопки прерывания ардуино

В Arduino Uno есть два пина, поддерживающие прерывания. Это цифровые пины 2 (int 0) и 3 (int 1). Один из них мы и будем использовать в нашей схеме.

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

Использование прерываний Arduino

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

К аналоговому пину A0 мы подключаем инфракрасный дальномер. И к пинам D9, D10 и D11 мы подключаем светодиоды через резисторы на 150 Ом.

Мы выбрали именно эти контакты для светодиодов, потому что они могут выдавать ШИМ сигнал.Теперь рассмотрим скетч:

123456789101112131415161718192021222324252627282930313233343536373839404142434445 // Назначение прерыванияint buttonInt = 0;// Переменные с пинами светодиодовint yellowLed = 11;int redLed = 10;int greenLed = 9;int nullLed = 6;volatile int selectedLed = greenLed;// Инфракрасный дальномерint distPin = 0;void setup () {// Устанавливаем режимы пиновpinMode(redLed, OUTPUT);pinMode(greenLed, OUTPUT);pinMode(yellowLed, OUTPUT);pinMode(nullLed, OUTPUT);// Устанавливаем прерываниеattachInterrupt(buttonInt, swap, RISING);}// Обработчик прерыванияvoid swap() {if(selectedLed == greenLed)selectedLed = redLed;else if(selectedLed == redLed)selectedLed = yellowLed;else if(selectedLed == yellowLed)selectedLed = nullLed;elseselectedLed = greenLed;}void loop () {// Получаем данные с дальномераint dist = analogRead(distPin);int brightness = map(dist, 0, 1023, 0, 255);// Управляем яркостьюanalogWrite(selectedLed, brightness);}

Обратите внимание на следующие моменты: Необходимо использовать ключевое слово «volatile» перед объявлением переменной значение которой будет изменяться в обработчике прерывания.

Так же я добавил переменную «nullLed» для того что бы программа на определенном шаге не меняла цвет ни одного из подключенных светодиодов. Строка «attachInterrupt(buttonInt, swap, RISING);» назначает обработчиком прерывания функцию swap.

Подробнее об этой функции вы можете почитать здесь.

Функция swap просто переключает текущий светодиод на следующий. Остальной скетч должен быть вам понятен, если вы посмотрели все предыдущие уроки. Это последний обучающий урок. В следующих статьях я расскажу о подключении к Arduino популярных модулей.

Источник: https://all-arduino.ru/arduino-dlya-nachinayushhih-urok-14-preryvaniya/

Использование прерываний на Arduino

Оптимизируйте ваши программы для Arduino с помощью прерываний – простого способа для реагирования на события в режиме реального времени!

Мы прерываем нашу передачу..

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

При обнаружении определенного сигнала прерывание (как и следует из названия) прерывает всё, что делал процессор, и выполняет некоторый код, предназначенный для реагирования на вызвавшую его внешнюю причину, воздействующую на Arduino.

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

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

Прерывания по кнопке

Начнем с простого примера: использования прерывания для отслеживания нажатия кнопки. Для начала, мы возьмем скетч, который вы, вероятно, уже видели: пример «Button», включенный в Arduino IDE (вы можете найти его в каталоге «Примеры», проверьте меню Файл → Примеры → 02. Digital → Button).

const int buttonPin = 2; // номер вывода с кнопкой const int ledPin = 13; // номер вывода со светодиодом int buttonState = 0; // переменная для чтения состояния кнопки void setup() { // настроить вывод светодиода на выход: pinMode(ledPin, OUTPUT); // настроить вывод кнопки на вход: pinMode(buttonPin, INPUT); } void loop() { // считать состояние кнопки: buttonState = digitalRead(buttonPin); // проверить нажата ли кнопка. // если нажата, то buttonState равно HIGH: if (buttonState == HIGH) { // включить светодиод: digitalWrite(ledPin, HIGH); } else { // погасить светодиод: digitalWrite(ledPin, LOW); } }

В том, что вы видите здесь, нет ничего шокирующего и удивительного: всё, что программа делает снова и снова, это прохождение через цикл loop() и чтение значения buttonPin.

Предположим на секунду, что вы хотели бы сделать в loop() что-то еще, что-то большее, чем просто чтение состояния вывода. Вот здесь и пригодится прерывание.

Вместо того, чтобы постоянно наблюдать за состоянием вывода, мы можем поручить эту работу прерыванию и освободить loop() для выполнения в это время того, что нам необходимо! Новый код будет выглядеть следующим образом:

const int buttonPin = 2; // номер вывода с кнопкой const int ledPin = 13; // номер вывода со светодиодом volatile int buttonState = 0; // переменная для чтения состояния кнопки void setup() { // настроить вывод светодиода на выход: pinMode(ledPin, OUTPUT); // настроить вывод кнопки на вход: pinMode(buttonPin, INPUT); // прикрепить прерывание к вектору ISR attachInterrupt(0, pin_ISR, CHANGE); } void loop() { // Здесь ничего нет! } void pin_ISR() { buttonState = digitalRead(buttonPin); digitalWrite(ledPin, buttonState); }

Циклы и режимы прерываний

Здесь вы заметите несколько изменений. Первым и самым очевидным из них является то, чтоloop() теперь не содержит никаких инструкций! Мы можем обойтись без них, так как вся работа, которая ранее выполнялась в операторе if/else, теперь выполняется в новой функцииpin_ISR().

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

При написании обработчика прерывания следует учитывать несколько важных моментов, отражение которых вы можете увидеть в приведенном выше коде:

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

Вам, наверное, интересно: откуда мы знаем, когда запустится прерывание? Что его вызывает? Третья функция, вызываемая в функции setup(), устанавливает прерывание для всей системы. Данная функция, attachInterrupt(), принимает три аргумента:

  1. вектор прерывания, который определяет, какой вывод может генерировать прерывание. Это не сам номер вывода, а ссылка на место в памяти, за которым процессор Arduino должен наблюдать, чтобы увидеть, не произошло ли прерывание. Данное пространство в этом векторе соответствует конкретному внешнему выводу, и не все выводы могут генерировать прерывание! На Arduino Uno генерировать прерывания могут выводы 2 и 3 с векторами прерываний 0 и 1, соответственно. Для получения списка выводов, которые могут генерировать прерывания, смотрите документацию на функцию attachInterrupt для Arduino;
  2. имя функции обработчика прерывания: определяет код, который будет запущен при совпадении условия срабатывания прерывания;
  3. режим прерывания, который определяет, какое действие на выводе вызывает прерывание. Arduino Uno поддерживает четыре режима прерывания:
    • RISING– активирует прерывание по переднему фронту на выводе прерывания;
    • FALLING– активирует прерывание по спаду;
    • CHANGE– реагирует на любое изменение значения вывода прерывания;
    • LOW– вызывает всякий раз, когда на выводе низкий уровень.

И резюмируя, наша настройка attachInterrupt()соответствует отслеживанию вектора прерывания 0 (вывод 2), чтобы отреагировать на прерывание с помощью pin_ISR(), и вызвать pin_ISR()всякий раз, когда произойдет изменение состояния на выводе 2.

Volatile

Еще один момент, на который стоит указать: наш обработчик прерывания использует переменную buttonStateдля хранения состояния вывода. Проверьте определение buttonState: вместо типа int, мы определили его, как тип volatile int.

В чем же здесь дело? volatileявляется ключевым словом языка C, которое применяется к переменным. Оно означает, что значение переменной находится не под полным контролем программы.

То есть значение buttonStateможет измениться и измениться на что-то, что сама программа не может предсказать – в этом случае, пользовательский ввод.

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

Так как переменная buttonStateне используется или не вызывается напрямую в функциях loop()или setup(), существует риск того, что компилятор может удалить её, как неиспользуемую переменную.

Очевидно, что это неправильно – нам необходима эта переменная! Ключевое слово volatile обладает побочным эффектом, сообщая компилятору, что эту переменную необходимо оставить в покое.

Удаление неиспользуемых переменных из кода – это функциональная особенность, а не баг компиляторов. Люди иногда оставляют в коде неиспользуемые переменные, которые занимают память. Это не такая большая проблема, если вы пишете программу на C для компьютера с гигабайтами оперативной памяти.

Однако, на Arduino оперативная память ограничена, и вы не хотите тратить её впустую! Даже C компиляторы для компьютеров будут поступать точно так же, несмотря на массу доступной системной памяти.

Зачем? По той же причине, по которой люди убирают за собой после пикника – это хорошая практика, не оставлять после себя мусор.

Подводя итоги

Прерывания – это простой способ заставить вашу систему быстрее реагировать на чувствительные к времени задачи.

Они также обладают дополнительным преимуществом – освобождением главного цикла loop(), что позволяет сосредоточить в нем выполнение основной задачи системы (я считаю, что использование прерываний, как правило, позволяет сделать мой код немного более организованным: проще увидеть, для чего разработан основной кусок кода, и какие периодические события обрабатываются прерываниями). Пример, показанный здесь, – это самый базовый случай использования прерываний; вы можете использовать для чтения данных с I2C устройства, беспроводных передачи и приема данных, или даже для запуска или остановки двигателя.

Есть какие-нибудь крутые проекты с прерываниями? Оставляйте комментарии ниже!

Оригинал статьи:

  • Nash Reilly. Using Interrupts on Arduino

Источник: https://radioprog.ru/post/114

Программирование Arduino – прерывания

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

уважаемый ДиХальт на доступных примерах объясняет, что такое Подпрограммы и прерывания 🙂

Arduino так же предоставляет свои функции для работы с прерываниями. Эти функции объявлены в файле hardwarecoresarduinowiring.h и реализованы в файле hardwarecoresarduinoWInterrupts.c Их всего две: attachInterrupt и DetachInterrupt.

void attachInterrupt(uint8_t, void (*)(void), int mode);

Описание: Определяет, какую функцию вызывать, когда происходит внешнее прерывание. Замещает предыдущую функцию, если таковая была привязана к данному прерыванию. Большинство плат Arduino/Freeduino имеют два внешних прерывания с номерами 0 (на digital pin 2) и 1 (на digital pin 3). Arduino Mega имеет дополнительно ещё четыре: с номерами 2 (pin 21), 3 (pin 20), 4 (pin 19) и 5 (pin 18).

Вызов:attachInterrupt(interrupt, function, mode);Параметры: interrupt: номер прерывания (int) function: функция, которая должны вызываться при прерывании. Функция не должна принимать параметров и не должна ничего возвращать. mode: определяет, когда должно сработать прерывание.

Определены следующие константы:

LOW — вызов прерывания всякий раз, когда на порту низкий уровень напряжения;

CHANGE – прерывание вызывается при изменении значения на входе;
RISING – вызов прерывания при изменении уровня напряжения с низкого (LOW) на высокое(HIGH)
FALLING – вызов прерывания при изменении уровня напряжения с высокого (HIGH) на низкое (LOW) Возвращаемое значение: ничего Пример:// // светодиод, подключённый к digital pin 13 будет изменять своё // состояние при изменении напряжения на digital pin 2 // int pin = 13; volatile int state = LOW; void setup() { pinMode(pin, OUTPUT); // порт как выход attachInterrupt(0, blink, CHANGE); // привязываем 0-е прерывание к функции blink(). } void loop() { digitalWrite(pin, state); // выводим state } void blink() { state = !state; // меняем значение на противоположное }

Примечание относительно использования volatile:

Т.о. переменная получается как бы «расшарена». Т.е. значение переменной могут изменять разные части программы — обработчики прерываний, подпрограммы, функции.

void detachInterrupt(uint8_t);

Описание: Отключает указанное прерывание. Вызов:detachInterrupt(interrupt);Параметры: interrupt: номер прерывания для отключения (0 или 1). Возвращаемое значение: ничего

читать далее:

Ссылки:

По теме:

Источник: http://robocraft.ru/blog/arduino/45.html

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

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

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

Большинство информации можно найти на страницах arduino.

cc по ходу текста буду добавлять ссылки, где я нашел информацию. Переходим к прерываниям.

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

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

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

Для работы с прерываниями нет необходимости подключать какие-либо библиотеки — все они уже включены автоматически.

Синтаксис:

attachInterrupt(interrupt, function, mode)

Параметры:
interrupt — номер вызываемого прерывания (0, 1 и т.д.).
function — называние функции, которая будет вызываться при срабатывании прерывания. Важно: Данная функция не должна получать параметры при запуске или возвращать какие-либо значения!
mode — режим. Возможные значения LOW, CHANGE, FALLING, RISING

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

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

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

/* http://compblog.vlukyanov.com */ #include int buttonInt = 0; int screenLed = 4; LiquidCrystal lcd(11, 10, 9, 8, 7, 6); void setup() { attachInterrupt(buttonInt, screenon, LOW); pinMode(screenLed, OUTPUT); lcd.begin(16, 2); digitalWrite(screenLed,HIGH); } void screenon() { lcd.print(“11111111111111111”); } void loop() { lcd.print(“00000000000000000”); delay(100); }

В основном цикле программы на экран выводятся нули, но когда на входе прерывания сигнал LOW (кнопка отпущена), на экран выводятся единицы.
Т.е. Нажатие на кнопку останавливает повторяющиеся прерывания и запускает основной цикл программы.

CHANGE — прерывание вызывается при смене сигнала от LOW к HIGH и наоборот. Функция выполняется только один раз при любой смене сигнала.

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

FALLING — прерывание вызывается когда значение меняется от HIGH к LOW. Разовый вызов функции, на каждый удовлетворяющий вызову переход.

Сигнал прерывания имеет внешнее по отношению к Arduino происхождение. Его можно сравнить с периферийными устройствами компьютера. Для демонстрации я использовал кнопку и думаю это весьма распространенный способ запуска прерывания.

Для демонстрации режима LOW я использовал следующую схему с тактовой кнопкой. —> Данная схема включает кнопку S1 и стягивающий резистор R1 на 10кОм. Резистор обеспечит отсутствие статических разрядов создающих «шум» и запускающих прерывание в случайном порядке.Но при этом есть один большой недостаток данной схемы — кнопка не сразу выдает значение HIGH/LOW когда была нажата/отпущена. Есть некоторые колебания сигнала перед тем как через кнопку пойдет/прекратит_движение ток.Вот тут есть хорошая статья как этого избежать.
Вышеприведенная схема подходит для демонстрации вызова прерывания по LOW, т.к. первичные колебания не значительны. Но она абсолютно не подходит для остальных режимов. Функция будет запущена множество раз, что может привести к некорректной работе программы. Чтоб правильно вызывать прерывания, необходимо позаботится о корректной передаче сигнала. Подробнее о схеме: S1 — Кнопка. R1 — Стягивающий резистор. C1 — Конденсатор на 1микрофарад для сглаживания сигнала иначе значения с кнопки так и будут скакать. OR1 — логическое или. Получив сигнал с кнопки сглаженный конденсатором он выдаст логическую единицу(HIGH) или ноль (LOW).Подойдет не только логический элемент «или», можно использовать «и», «не», триггер Шмидта или инвертирующий триггер Шмидта. Эти компоненты среагируют только при переходе сигналом определенной границы.

Теория на этом заканчивается. Переходим к практике.

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

Кнопка подключена по первой схеме указанной выше. Поэтому имеет все описанные недостатки.

Скетч interrupt.ino

/* Interrupt / Прерывания http://compblog.vlukyanov.com */ byte intPin = 0; // Номер прерывания, которое будет вызыватся. // Не номер контакта! // Контакт которому соответствует прерывание 2. byte ledPin = 3; // Контакт с ШИМ для подключения светодиода. volatile int x = 0; // Переменная для подсчета времени, // когда необходимо вновь запустить изменение яркости диода. // Обязательно указание директивы volatile, иначе могут появится ошибки. // volotile означает возможность внезапного, для основной программы, изменения переменной. // В данном случае изменение произойдет в прерывании. // Основные параметры платы. void setup() { attachInterrupt(intPin,pause,RISING); // Параметры прерывания. pinMode(ledPin, OUTPUT); } // Функция которая будет выполнятся при нажатии на кнопку. void pause() { x = millis()/1000+3; // К текущему времени работы платы добавляем 3 секунды и запомиаем. // millis – функция, возвращающая время работы платы в миллисекундах. } // Основная программа. void loop() { for (int i = 0; i millis()/1000) delay(100); // х может быть больше текущего времени только если вызывалось прерывание. // Проверка на прошествие трех секунд, проводится 10 раз в секунду. // Данный цикл крутится пока не пройдет три секунды, значение светодиода не изменяется. } }

С прерываниями связанна еще одна важная функция

detachInterrupt(interrupt)

Запрещает вызов определенного прерывания.
Может использоваться когда необходимо выполнять какие-либо действия без остановки на выполнение прерываний.

Ссылки:
http://arduino.cc/en/Reference/AttachInterrupt
http://www.labbookpages.co.uk/electronics/debounce.html

P.s. Пока писал эту статью привезли тригеры Шмидта, регистры и прочие логические микросхемы.

Источник: http://compblog.VLukyanov.com/?p=435

Arduino — основы программирования

После ознакомления с основными элементами Arduino, а также написания программы “Hello World!” пришло время для знакомства с языком программирования.

Структура языка основана главным образом на C/C++, поэтому те, кто ранее программировал на этом языке, не будут испытывать затруднений при освоении программирования Arduino. Остальные должны освоить основную информацию о командах управления, типах данных и функциях.

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

Внимание: Рекомендуется ознакомиться (хотя бы бегло) с курсом C++, а именно с системой счисления, алгоритмами, записи кода…

Основы основ

Несколько формальных вещей, то есть таких, о которых все знают, но иногда забывают…

В Arduino IDE, как в C/C++, необходимо помнить о регистрах символов. Ключевые слова, такие как if, for всегда записываются в нижнем регистре. Каждая инструкция заканчивается на «;». Точка с запятой сообщает компилятору, какую часть интерпретировать как инструкцию.

Скобки {..} используются для обозначения программных блоков. Мы используем их для ограничения тела функции (см. ниже), циклов и условных операторов.

Хорошей практикой является добавление комментариев к содержимому программы, это помогает легко понять код. Однострочные комментарии начинаются с // (двойная косая черта). Многострочные комментарии начинаются с /* и заканчиваются на */

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

#include // стандартная библиотека #include “svoya_biblioteka.h” // библиотека в каталоге проекта

Функции в Arduino

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

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

Объявление функции

Схема объявления функции выглядит следующим образом:

тип имя_функции(параметр) { // инструкции для выполнения (тело функции) return (/* возвращение значения*/); }

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

После исполнения, функция вернет значение объявленного типа. В случае, если функция не принимает никакого возвращаемого значения, то тип данных будет “void”.

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

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

Внутри скобок «{…}» содержится собственно тело функции или инструкция, которые мы хотим выполнить. Описание конкретных инструкций укажем в отдельной статье.

Все функции, возвращающие значение, заканчиваются оператором return, за которым следует возвращаемое значение. Только функции, объявленные нулевым указателем (“void”), не содержат оператор return. Необходимо знать, что оператор return завершает выполнение функции независимо от местоположения.

Ниже приведены некоторые примеры деклараций функций.

 void f1() { //тело функции } —————————————— int minus() { //тело функции return (0); } —————————————— int plus(int a, int b) { return (a+b); }

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

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

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

Вызов функции

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

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

f1(); plus(2,2); y=plus(1,5);

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

Если функция f1() объявлена без параметров, то при ее вызове нельзя указывать никакие параметры, т.е. вызов функции f1(0) будет неверным.

Функция plus(int a, int b) требует ровно двух параметров, поэтому вызов с одним или тремя параметрами невозможно.

Вызов y=plus(1,5) приведет к выполнению функции «plus» с параметрами «1» и «5» и сохранить возвращаемое значение в переменную «y».

Функции setup() и loop()

Обладая знаниями об объявлении и вызове функций, мы можем перейти к системным функциям Arduino: setup() и loop(). Arduino IDE в обязательном порядке необходимо объявлять эти две функции.

setup () – это функция, которая вызывается автоматически при включении питания или нажатии кнопки RESET.

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

void setup () { // тело функции – инициализация системы }

loop () – это функция, которая вызывается в бесконечном цикле. Данная функция также не возвращает значения и не вызывается с параметрами. Ниже показано правильное объявление функции loop():

void loop () { // тело функции – программный код }

Как вы видите, объявление функции loop () идентично объявлению функции setup (). Различие состоит в выполнении этих функций микроконтроллером.

Теперь мы проанализируем следующий псевдокод:

void setup () { on_led1 ();    //включаем светодиод led1 off_led1 ();    //выключаем светодиод led1 } void loop () { on_led2 ();    //включаем светодиод led2 off_led2 ();    //выключаем светодиод led2 }

В функции setup () есть две инструкции: первая включает светодиод led1, подключенный к плате (например, контакт 13), а вторая выключает светодиод led1.

Функция loop () имеет идентичные инструкции для включения и выключения светодиода led2, подключенного к плате (например, контакт 12).

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

Нажатие кнопки RESET приведет к тому, что led1 снова мигнет один раз, а led2 снова начнет постоянно мигать.

Подведем итог:

  • Функции setup () и loop () – это системные функции, которые должны быть определены в каждом проекте. Даже в ситуации, когда в одном из них мы не пропишем какой-либо код, мы все равно должны объявить эти две функции;
  • Функция setup () выполняется один раз, loop() выполняется непрерывно;
  • Мы создаем собственные функции в одном файле;
  • Мы можем вызвать свои функции как из setup () и loop (), так и из других функций;
  • Наши собственные функции можно вызывать с параметрами и возвращать значение;
  • Вызов функции должен быть совершен в соответствии с ее декларацией.

Источник: http://www.joyta.ru/10709-arduino-osnovy-programmirovaniya/

Работа с Arduino

Когда у меня возникло желание вести разработку под Arduino, я столкнулся с несколькими проблемами:

  • Выбор модели из списка доступных
  • Попытки понять, чего мне понадобится кроме самой платформы
  • Установка и настройка среды разработки
  • Поиск и разбор тестовых примеров
  • «Разборки» с экраном
  • «Разборки» с процессором

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

Выбор платформы

Перед началом программирования под железяку требуется в начале ее купить. И тут я столкнулся с первой проблемой: оказалось, что разных *дуин довольно много. Тут есть и широкая линейка Arduino и примерно такая же широкая Freeduino и другие аналоги.

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

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

Я выбрал платформу Arduino Leonardo как самую доступную по цене и имеющуюся на тот момент в Интернет магазине, в котором я всё и заказывал. Отличается она от остальной линейки тем, что у нее на борту установлен только один контроллер, который занимается и работой с USB-портом и выполнением тех самых задач, которые мы на наше устройство повесим.

У этого есть свои плюсы и минусы, но напороться на них при первоначальном изучении не получится, поэтому забудем о них пока. Оказалось, что она подключается к компьютеру через micro-USB, а не USB-B, как вроде бы большинство других.

Это меня несколько удивило, но и обрадовало, потому что я, как владелец современного устройства на Android'е без этого кабеля вообще из дома не выхожу.
Да, питается почти любая *дуино-совместимая железяка несколькими способами, в том числе, от того же кабеля, через который программируется.

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

Спектр задач

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

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

Список их довольно длинный и многообещающий, и ищутся они в Интернете по слову shield.

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

Постановка задачи

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

Сейчас у меня под рукой был очень похожий на составную часть мины из какого-нибудь голливудского кинофильма экран со всеми необходимыми кнопками, с которым надо было научиться работать, а также очень хотелось освоить работу с прерываниями (иначе в чем смысл использования контроллера?) поэтому первым же, что пришло в голову, оказалось написать часы. А поскольку размер экрана позволял, то еще и с датой.

Первые шаги

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

Скрытый текст

Единственное скажу, что вспомнив молодость (а точнее первый «проект», собранный во время изучения радиоэлектроники во Дворце пионеров — мультивибратор с двумя светодиодами), я нашел 2 светодиода и поправил приведенный в статье пример и начал мигать ими :).

«Вторые шаги»

Следующим закономерным вопросом для меня стало «как работать с LCD экраном?». Официальная страница устройства любезно предоставила мне ссылки на архив, в котором оказалось 2 библиотеки с замечательными примерами. Только не сказала, что с этим всем делать. Оказалось, что содержимое нужно просто распаковать в папку libraries среды разработки.

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

Использованного в примере набора команд в принципе достаточно для простой работы с экраном, но если захочется чего-то большего, то можно открыть исходный текст библиотек LCDKeypad и LiquidCrystal и посмотреть что там есть еще.

Архитектура программы

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

Всё остальное можно вынести в тело основной программы. А этого самого «остального» у нас довольно много — вся работа с интерфейсом.

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

Проблемы, связанные с таким подходом

Периодические изменения экрана

Очень хотелось сделать мигающие двоеточия между часами, минутами и секундами, чтобы как в классических часах они пол секунды горели, а пол — нет. Но поскольку экран перерисовывается всё время, надо было как-то определять в какую половину секунды их рисовать, а в какую — нет. Самым простым оказалось сделать 120 секунд в минуте и рисовать двоеточия каждую нечетную секунду.

Мелькания

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

LCDKeypad lcd;
void showTime(){ lcd.home(); if (hour

Источник: http://www.pvsm.ru/arduino/27562

Ссылка на основную публикацию
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}