Прерывания, прерывания и еще раз прерывания. таймеры

AVR. Учебный курс. Таймеры

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

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

И такой счетчик есть, даже не один — это периферийные таймеры. В AVR их может быть несколько штук да еще с разной разрядностью. В ATmega16 три, в ATmega128 четыре. А в новых МК серии AVR может даже еще больше, не узнавал.

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

Что умееют таймеры

  • Тикать с разной скоростью, подсчитывая время
  • Считать входящие извне импульсы (режим счетчика)
  • Тикать от внешнего кварца на 32768гц
  • Генерировать несколько видов ШИМ сигнала
  • Выдавать прерывания (по полудесятку разных событий) и устанавливать флаги

Разные таймеры имеют разную функциональность и разную разрядность. Это подробней смотреть в даташите.

Источник тиков таймера
Таймер/Счетчик (далее буду звать его Т/С) считает либо тактовые импульсы от встроенного тактового генератора, либо со счетного входа.

Погляди внимательно на распиновку ног ATmega16, видишь там ножки T1 и T0?

Так вот это и есть счетные входы Timer 0 и Timer 1. При соответствующей настройке Т/С будет считать либо передний (перепад с 0-1), либо задний (перепад 1-0) фронт импульсов, пришедших на эти входы.

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

Кроме того, Т/С2 способен работать в асинхронном режиме. То есть Т/С считает не тактовые импульсы процессора, не входящие импульсы на ножки, а импульсы своего собственного собственного генератора, работающего от отдельного кварца. Для этого у Т/С2 есть входы TOSC1 и TOSC2, на которые можно повесить кварцевый резонатор.

Зачем это вообще надо? Да хотя бы организовать часы реального времени. Повесил на них часовой кварц на 32768 Гц да считай время — за секунду произойдет 128 переполнений (т.к. Т/С2 восьми разрядный). Так что одно переполнение это 1/128 секунды. Причем на время обработки прерывания по переполнению таймер не останавливается, он также продолжает считать. Так что часы сделать плевое дело!

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

То есть еще до попадания в счетный регистр частота импульсов будет делиться. Делить можно на 8, 32, 64, 128, 256, 1024. Так что если повесишь на Т/С2 часовой кварц, да пропустишь через предделитель на 128, то таймер у тебя будет тикать со скоростью один тик в секунду.

Удобно! Также удобно юзать предделитель когда надо просто получить большой интервал, а единственный источник тиков это тактовый генератор процессора на 8Мгц, считать эти мегагерцы задолбаешься, а вот если пропустить через предделитель, на 1024 то все уже куда радужней.

Но тут есть одна особенность, дело в том, что если мы запустим Т/С с каким нибудь зверским предделителем, например на 1024, то первый тик на счетный регистр придет не обязательно через 1024 импульса.

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

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

Например первый таймер работает на выводе 1:64, а второй на выводе 1:1024 предделителя.

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

Что произойдет? Правильно, у второго делилка тут же скинется в 0 (предделитель то единый, регистр у него один) и второму таймеру придется ждать еще 1024 такта, чтобы получить таки вожделенный импульс!

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

Для сброса предделителей достаточно записать бит PSR10 в регистре SFIOR. Бит PSR10 будет сброшен автоматически на следующем такте.

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

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

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

Чувствуете лажу? Вот! Таймер точное устройство, поэтому грузить его счетные регистры надо одновременно! Но как? А инженеры из Atmel решили проблему просто:
Запись в старший регистр (TCNTxH) ведется вначале в регистр TEMP. Этот регистр чисто служебный, и нам никак недоступен.

Что в итоге получается: Записываем старший байт в регистр TEMP (для нас это один хрен TCNTxH), а затем записываем младший байт. В этот момент, в реальный TCNTxH, заносится ранее записанное нами значение. То есть два байта, старший и младший, записываются одновременно! Менять порядок нельзя! Только так

Выглядит это так:

1
2
3
4
CLI ; Запрещаем прерывания, в обязательном порядке! OUT TCNT1H,R16 ; Старшей байт записался вначале в TEMP OUT TCNT1L,R17 ; А теперь записалось и в старший и младший! SEI ; Разрешаем прерывания

CLI ; Запрещаем прерывания, в обязательном порядке! OUT TCNT1H,R16 ; Старшей байт записался вначале в TEMP OUT TCNT1L,R17 ; А теперь записалось и в старший и младший! SEI ; Разрешаем прерывания

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

Тогда в его регистрах будет не то что мы послали тут (или в прерывании), а черти что.

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

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

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

Итак, главным регистром является TCCRx
Для Т/С0 и Т/С2 это TCCR0 и TCCR2 соответственно, а для Т/С1 это TCCR1B

Нас пока интересуют только первые три бита этого регистра: CSx2.. CSx0, вместо х подставляется номер таймера.

Они отвечают за установку предделителя и источник тактового сигнала.

Источник: http://easyelectronics.ru/avr-uchebnyj-kurs-tajmery.html

Учебный курс. Понятие прерывания. Подаем голос микроконтроллером

______________________________  Прерывание  ___________________________

    Представьте себе ситуацию. Вы сидите на работе и корпите над очередной микроконтроллерной програмулиной. Подходит к вам начальник и говорит: “Слушай, Паш, нам  осциллографы в отдел закупили – Tektronix, четырехканальные. Помоги Васе  притащить их”. Вы думаете: ”Ну, е-мое, только мысль поперла.. и на тебе”.

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

    Довольно просто, но есть ряд принципиальных моментов.

Во-первых:-    вы делали свою работу-    параллельно кто-то покупал осциллографы-    по наступлению события «осциллографы закупили» – вы прерываете выполнение своей работы-    некоторое время вы занимаетесь другой работой – тащите осциллографы-    потом вы возвращаетесь на рабочее место и продолжаете делать свою работу с того места, на котором остановилисьВо-вторых:-    вы вполне могли бы послать начальника и никуда не идти-    уйдя за осциллографами, вы могли задержаться там надолго, а то и вовсе не вернуться

–    вернувшись на рабочее место, вы могли бы уже позабыть свои гениальные идеи

    Все это очень похоже на то, что происходит в микроконтроллере. Микроконтроллеры AVR имеют в своем составе целую тучу периферийных устройств (таймеры/счетчики, аналого-цифровой преобразователь, аналоговый компаратор, асинхронный приемопередатчик…и т.д). Мощь микроконтроллера в том, что все эти устройства могут работать параллельно и независимо друг от друга, а также параллельно выполняемой программе. Каждое периферийное устройство может вызывать прерывание по наступлению определенного события. Прерывание будет происходить только в том случае, если оно разрешено. Разрешение прерываний устанавливается для каждого устройства отдельно. Помимо этого есть флаг глобального разрешения/запрещения всех прерываний – это I флаг в регистре SREG. При возникновении прерывания микроконтроллер сохраняет содержимое счетчика команд PC в стеке, то есть запоминает место, на котором его прервали.  Загружает в счетчик команд адрес соответствующего вектора прерывания и переходит на этот адрес. Попадает на команду безусловного перехода по которой переходит на подпрограмму обработки прерывания. Запрещает прерывания сбросом флага I, выполняет подпрограмму. Выполнив подпрограмму обработки прерывания, микроконтроллер разрешает прерывания, устанавливая флаг I, и восстанавливает содержимое счетчика команд, то есть возвращается на то же место программы, на котором его прервали.    По идее, обработчик прерывания не должен повреждать содержимое регистров микроконтроллера, потому что они могут содержать данные программы выполняемой в тот момент. Для этого в начале подпрограммы обработки прерывания содержимое регистров микроконтроллера сохраняют в стеке, а в конце подпрограммы восстанавливают. Таким образом, выйдя из прерывания, микроконтроллер сможет продолжить выполнение программы, как ни в чем не бывало. Программируя на ассемблере, сохранение регистров прописывает сам программист, на Си – это делает компилятор.

_______________________________________________________________

   Теперь поговорим о таймере. ATmega8535 имеет на борту три  таймера/счетчика – два восьмиразрядных (T0, T2) и один шестнадцатиразрядный (T1). Мы будем использовать восьмиразрядный таймер/счетчик T0. В состав этого таймера входят три регистра – регистр управления TCCR0, счетный регистр TCNT0 и регистр сравнения OCR0.

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

Таймер T0 может вызвать прерывание по наступлению события “переполнение” – это когда переполняется счетный регистр TCNT0 и по наступлению события “совпадение” – это когда значение счетного регистра TCNT0 становится равным значению регистра сравнения OCR0. Флаги разрешающие эти прерывания находятся в регистре TIMSK.

 
    Мы настроим таймер/счетчик Т0 так, чтобы он вызывал прерывание по событию “совпадение” с частотой 5 кГц. В функции обработчике будем инвертировать состояние вывода микроконтроллера, к которому подключен пьезодинамик. Таким образом, частота пищания пьезика будет равна 2,5 кГц.  (Подключен именно пьезодинамик! Не перепутайте.

У пьезодинамика сопротивление зависит от частоты и на 2,5 КГц оно обычно еденицы Ком, поэтому его можно подключать к выводу микроконтроллера напрямую, без ограничительного резистора).

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

//Подаем голос микроконтроллером AVR

#include 
#include

int main(void)

{

//настраиваем порты ввода-вывода

  DDRD = (0

Источник: http://chipenable.ru/index.php/item/5

AVR Studio: как написать обработчик прерывания

Источник: http://microsin.net/programming/AVR/avr-studio-isr.html

Прерывание таймера по сравнению, программирование МК

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

Если потом запустить таймер, то больше об этом можно не думать — все будет происходить автоматиче­ски.

Поскольку в Tiny2313 и большинстве моделей Mega (если не во всех) все таймеры, в том числе и 8-разрядные, имеют такой режим (в «классиче­ских» его имел только 16-разрядный Timer 1), то применение его тем более целесообразно.

Подробности

Прерывание по сравнению происходит в момент, когда счетчик досчитывает до наперед заданного значения, хранящегося в специальном регистре сравне­ния. Есть одна тонкость; связанная с этим режимом. Входной тактовый сигнал счетчика обычно делится в соответствии с заданным коэффициентом деле­ния, в наших примерах это 1/64.

Поэтому в нашем случае каждое состояние счетчика остается неизменным в течение 64 тактов процессора. Так в какой именно момент возникает прерывание — сразу, как только счетчик досчитал до заданного значения? Или в момент, когда это заданное значение должно уже смениться следующим? Если предположить, что в AVR все устроено, как на рис. 16.

13, 6, то имеет место первый случай: счетчик начинает новый отсчет с первого системного такта сразу после совпадения величин. Тогда состояния счетчика получаются неравноправными: все они длятся по 64 системных такта (в нашем случае), кроме последнего, который длится один системный такт не­зависимо от коэффициента деления.

Так это было устроено в «классической» серии AVR. А вот для счетчиков семейств Меда и Tiny все иначе: там событие совпадения наступает по последнему такту при совпадении, в момент, когда состояние счетчика должно уже смениться.

Поэтому, если вы зададите в реги­страх сравнения, к,примеру, число 2, то при коэффициенте деления 1/64 Timer 1 в МК AT90S2313 отсчитает до возникновения прерывания 129 систем­ных тактов (или примерно 2 такта входной частоты счетчика), а в МК ATtiny2313 — 192 системных такта (ровно 3 такта входной частоты).

Таким об­разом, в первом случае коэффициент деления входной частоты таймера в ре­жиме сравнения равен установленному числу N плюс один такт системного ге­нератора, а во втором — числу Л/ + 1.

Причем в этом режиме можно упростить программу предельно: один из вы­водов контроллера (при прерывании с применением регистра сравнения А это 0с1а или вывод 15 для 2313) можно включить в режим автоматического переключения в момент совпадения, без дополнительного программного управления.

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

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

Для того чтобы установить режим сравнения, нужно вместо прерывания по переполнению разрешить прерывание по сравнению А (бит ocieia в регист­ре timsk), а также установить значение в регистрах сравнения (ocriah и ocrial), с которым будет сравниваться содержимое счетчика. Нетрудно до­гадаться, что для цикла счета в полсекунды оно равно вычисленному нами ранее значению 31 250. При записи в эти регистры нужно помнить то, что было сказано ранее про обращение с 16-разрядными регистрами в таймерах.

Есть в этом деле и еще один нюанс.

Что будет происходить с таймером после того, как значения в счетных регистрах и регистрах сравнения станут одина­ковыми (кроме того, что произойдет прерывание)? Ясно, что тут могут быть варианты: таймер может продолжить счет, обнулиться, установиться в какое-то наперед заданное значение и т. п. Это поведение настраивается: для выбо­ра режима обнуления (чтобы после сравнения таймер пришел бы в исходное состояние) следует установить бит wgmi2 (в «классической» версии МК он назывался стс1) — бит номер 3 в регистре tccrib.

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

/программа мигания светодиода /процессор Tiny2313, частота 4 МГц .include “tn8535def.inc” .def temp = rl6 /рабочий регистр

rjmp RESET /Reset Handler

reti /EXT_INTO / External InterruptO Handler reti /EXT_INT1 / External Interruptl Handler reti /TIM1_CAPT / Timer 1 Capture Handler rjmp /TIMl_COMPA ; Timer 1 CompareA Handler reti TIMl^OVF / Timer 1 Overflow Handler reti /TIM0_OVF / Timer 0 Overflow Handler reti /USARTO^RXC / USARTO RX Conplete Handler reti /USARTO^DRE / USARTO, UDR Eitpty Handler reti /USARTO_TXC / USARTO TX Conplete Handler reti /ANA_CCMP / Analog Comparator Handler reti /PCINT / Pin Change Interrupt reti /TIMERl_ca4PB / Timer 1 Compare В Handler reti /TIMERO_CCeylPA / Timer 0 Compare A Handler reti /TIMERO_CCeylPB / Timer 0 Conpare В Handler reti /USI_START / USI Start Handler reti /USI_OVERFLOW / USI Overflow Handler reti /EE_READY / EEPROM Ready Handler reti /WDT_OVERFLOW / Watchdog Overflow Handler

TIM1_C0MPA: /процедура обработки прерьшания по сравнению А /начало мигания

in temp,PortD /загружаем в temp состояние PortD

sbrc tenp,5 ;если PD5 равен О, след. команду пропускаем

rjnp set__Green /переход на установку зеленого

sbi PortD, 5 ;PD5»^1, устанавливаем красный

cbi PortD,6 ;PD6=0, устанавливаем красный

reti ;выход из прерывания set_Green: ;метка установка зеленого

cbi PortD,5 ;PD5=0, устанавливаем зеленый

sbi PortD,б ;PD6=1, устанавливаем зеленый reti ;выход из прерывания RESET: /процедура после сброса

ldi temp,low(RAMEND) /загрузка указателя стека

out SPL,teinp

ldi temp,ObOliooooo /устанавливаем биты 5 и б

out ddrd,temp /выводим это значение в регистр направления порта d

ldi temp,high(31250) /значение регистра сравнения для 0,5 сек

out ocriah,temp /старший байт

ldi tenp,low(31250)

out OCRIAL, tenp /младший байт

ldi tenp,0b00001011

out tccrib,tenp /запуск таймера с делителем 1/64

/и установка бита 3 «очищать счетчик при совпадении» ldi temp, (1«0CIE1A) /разрешение прерывания по сравнению А out TIMSK, tenp

sei /общее разрешение прерываний Cykle:

rjnp Cykle /зацикливаем процессор

Естественно, значение, загружаемое в регистры сравнения ocriah : ocrial, необязательно должно быть равно в точности 31 250. Это дает удобный спо­соб для точной подстройки интервала времени, который может иметь опре­деленный разброс из-за неточностей используемого кварца. Но мы займемся этим уже в следующей главе.

Источник: http://nauchebe.net/2010/06/preryvanie-tajmera-po-sravneniyu-programmirovanie-mk/

Interrupt (Прерывание)

Код прерывания (Interrupt)

Прерывает выполнение программы по одному из внешних или временных событий.

При срабатывании прерывания запускается составленый пользователем макрос.

Количество и тип прерываний зависит от модели микроконтроллера.

Что бы увидеть какие прерывания доступны конкретному контроллеру, вызовите иконку Interrupt и посмотрите список Interrupt on:

Обычно доступны следующие прерывания:

  • TMR- переполнение – событие по переполнению счетчика.
  • INT – событие по изменению в регистре входа.
  • Port change – событие по изменению в порте.
  • Custom – собственный код прерывания.

Создание прерывания

Добавьте иконку прерывания в проект.Выберите Enable Interrupt или Disable Interrupt для включения или выключения прерывания.Выберите доступный для этого контроллера тип прерывания из списка.Выберите параметры прерывания кнопкой Properties….

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

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

TMR Таймер по переполнению (напр. TMR0)

Прерывание по переполнению таймера. Счетчик базируется на внутреннем такте генератора и предварительным делении (ниже более детально). Когда счетчик достигает 0 срабатывает триггер и наступает прерывание.

Это приводит к выполнению макроса (подпрограммы). Прерывание по переполнению повторяется бесконечно с интервалом установленным в настройках.

(для примера на картинке выше, прерывание будет происходить 75 раз в секунду)

Пример прерывания TMR0:

Clock source select (Выбор источника счета)

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

Опции в зависимости от контроллера.

Пример:

  • Internal clock (CLK0) (внутренний генератор)
  • Transition on T0CKI (внешний генератор подключен к входу TOCKI)

Source edge select (Выбор фронта импульса)

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

  • High-to-low transition on T0CKI (нисходящий фронт)
  • Low-to-high transition on T0CKI (восходящий фронт)

Prescaler rate (Предварительное деление)Выберите предварительное деление сигнала счета.Предварительное деление позволяет выбрать частоту переполнения счетчика.

Например для чипа PIC16F877A значения могут лежать в пределах от 1:1 до 1:256.

Частота переполнения напрямую зависит от частоты тактового генератора.

Например.

  • Частота кварца:- 19660800
  • Деление:- 1:256
  • Частота прерывания 75Hz
  • Частота кварца:- 19660800
  • Деление:- 1:64
  • Частота прерывания 300Hz

INT (Счетчик)Прерывание вызывает смена сигнала на входе счетчика (INT) (например. нажатие кнопки)

Прерывание может быть вызвано:

  • Falling edge of INT (нисходящим сигналом)
  • Rising edge of INT (восходящим сигналом)

Port change (Изменение в порте)

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

Custom (Пользовательский)

Позволяет составить свое собственное прерывание.
Некоторые контроллеры имеют прерывания не представленные в программе Flowcode. В этом меню можно составить код своего прерывания.

Источник: http://flowcode.info/modules-articles/34-icons/228-interrupt

AVR: Обработка внешних прерываний

Прерывание — это событие по которому прерывается исполнение основного кода программы ( например функции main) и управление передаётся функции обработчику прерывания. Соответственно внешние прерывания — это  некие внешние события прерывающие исполнение основного кода программы.

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

Принцип работы внешних прерываний в AVR

Для того что бы микроконтроллер узнал о внешних событиях используются дискретные входы INT0  INT1 и т.д. Дискретные означает что они работают с логическими уровнями: 0 и 1. 0 — это отсутствие напряжения на входе

1 — наличие  на входе напряжения, которое равно напряжению питания микроконтроллера.

atmega8

Внешние прерывания можно разделить на два типа:

  • внешние прерывания по уровню
  • внешние прерывания по фронту

Внешние прерывания по уровню

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

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

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

Внешние прерывание по фронту

Прерывание по переднему фронту или, как иногда говорят, нарастанию сигнала, возникает когда происходит изменение уровня сигнала на входе INT с 0 на 1.

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

оно будет возникать и по переднему и по заднему фронту.

Настройка внешних прерываний в AVR

Внешние прерывания в avr atmega8 настраиваются при помощи бит ISCxx регистра MCUCR.

Зависимость условия срабатывания внешнего прерывания INT0 от бит ISC0x регистра MCUCR в avr atmega8

 ISC01 ISC00Условие срабатывания

Микроконтроллеры без использования прерываний практически ничего не стоят.

Прерывание (interrupt) получило свое название из того факта, что при срабатывании прерывания по какому-нибудь событию нормальное выполнение программы прерывается, и выполняется особый код – код обработчика прерывания (interrupt handler).

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

После того, как обработчик прерывания завершит свою работу, ядро микроконтроллера вернется к исполнению основной программы точно в той точке, где она была прервана прерыванием. События прерывания обычно происходят от каких-то внешних асинхронных воздействий на микроконтроллер – например, переключение логического уровня на внешнем выводе порта, либо переполнение регистра таймера, что означает истечение заданного интервала времени, и т. д.

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

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

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

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

Вместо проверки событий на возникновение микроконтроллер имеет возможность прервать нормальный поток программы при возникновении события и переключиться на действия обработчика события прерывания (ISR, interrupt service routine), затем вернуться обратно и продолжить выполнение основной программы.

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

[Обработчик прерывания на C]

Это самый простой вариант. В данном примере используется следующий алгоритм – основная программа настраивает таймер 1 и запускает обработчик прерывания. Этот обработчик срабатывает раз в секунду и заботится о себе сам, подстраивая величину счетчика TCNT1 (чтобы прерывания происходили точно раз в секунду).

Обработчик раз в секунду также декрементирует счетчик времени timer, который устанавливается и отслеживается в основной программе. Прерывание таймера разрешено постоянно (разрешается при старте программы). Таким образом, основная программа может получить любую задержку времени в интервале от 1 до 255 секунд.

Процесс по шагам:

1. Настраиваем таймер и разрешаем прерывание для него. Этот код должен вызываться однократно, при старте программы. Для этого можно написать отдельную процедуру, например:

#include ..   void SetupTIMER1 (void) { //На частоте тактов 16 МГц прерывание переполнения T/C1 // произойдет через (счет до 65535): // 1

 0  0  Низкий уровень на INT0
 0  1  Любое изменение на INT0
 1  0  Задний фронт на INT0
 1  1  Передний фронт на INT0

Для внешнего прерывания INT1, настройка производиться так же, только используются биты ISC11 ISC10.

Пример настройки внешнего прерывания для avr atmega8:

//сбрасываем все биты ISCxx
MCUCR &= ~( (1< //настраиваем на срабатывание INT0 по переднему фронту
MCUCR |= (1<

//сбрасываем все биты ISCxx MCUCR &= ~( (1<

Разрешение внешних прерываний в avr atmega

Для того что бы внешние прерывания заработали их надо разрешить, установив в 1 соответствующие биты в регистре GICR.

Бит INT0 отвечает за разрешение/запрещение  внешнего прерывания INT0, а бит INT1, соответственно за внешне прерывание INT1.

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

Пример кода разрешающего внешнее прерывание INT0 для avr atmega8:

//разрешаем внешнее прерывание INT0
GICR |= (1< //выставляем флаг глобального разрешения прерываний
sei();

//разрешаем внешнее прерывание INT0 GICR |= (1<

Пример использования внешних прерываний в AVR atmega

В качестве примера приведу программу счетчика импульсов. Программа подсчитывает количество импульсов на входе INT0, и раз в секунду выводит результат подсчета в uart.

#include
#include
#include
#include
 
//переменная счетчик
volatile unsigned long int0_cnt = 0;
 
//настройка внешнего прерывния INT0
void int0_init( void )
{
  //настраиваем на срабатывание INT0 по переднему фронту
  MCUCR |= (1<   //разрешаем внешнее прерывание INT0
  GICR |= (1< }
 
//функция обработчик внешнего прерывания INT0
ISR( INT0_vect )
{
  int0_cnt++;
}
 
//настройка UART
void uart_init( void )
{
  //настройка скорости обмена
  UBRRH = 0;
  UBRRL = 3; //115200 при кварце 7.3728 МГц
  //8 бит данных, 1 стоп бит, без контроля четности
  UCSRC = ( 1 << URSEL ) | ( 1 << UCSZ1 ) | ( 1 << UCSZ0 );
  //разрешить прием и передачу данных
  UCSRB = ( 1 << TXEN ) | ( 1 < }
 
//передача байта по UART
int uart_putc( char c, FILE *file )
{ //ждем окончания передачи предыдущего байта while( ( UCSRA & ( 1 << UDRE ) ) == 0 ); UDR = c; return 0;
}
 
FILE uart_stream = FDEV_SETUP_STREAM(uart_putc, NULL, _FDEV_SETUP_WRITE );
 
int main( )
{ //временная переменная unsigned long tmp;
  stdout = &uart_stream;
  int0_init(); uart_init();
  sei();
  while(1) { //на время копирования значения счетчика запрещаем прерывания cli(); tmp = int0_cnt; //разрешаем прерывания sei(); printf( “int0_cnt = %lu
“, tmp ); //пауза 1 секунда _delay_ms( 1000 ); } return 0;
}

#include #include #include #include

h> //переменная счетчик volatile unsigned long int0_cnt = 0; //настройка внешнего прерывния INT0 void int0_init( void ) {   //настраиваем на срабатывание INT0 по переднему фронту   MCUCR |= (1< “, tmp ); //пауза 1 секунда _delay_ms( 1000 ); } return 0; }

Скачать исходники примера работы с внешними прерываниями в avr atmega8 для avr-gcc ( winavr ) можно тут

Запись опубликована в рубрике Микроконтроллеры avr с метками atmega, avr, interrupt. Добавьте в закладки постоянную ссылку.

Источник: http://mainloop.ru/avr-atmega/avr-external-interrupt.html

Прерывания микроконтроллеров семейства AVR

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

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

Такие хоть и есть, НО!!! Из-за того, что многие в основном ленивые проггеры начинают работу с компилятором CV-AVR, в котором хоть и есть предварительный редактор кода с помощью которого перед написанием программы можно предварительно сконфигурировать те же таймеры, модули USART, TWI, но все же крайне не удобный интерфейс, с моей точки зрения и вообще CV-AVR мне крайне не симпатичен. Другое дело AVR Studio в связке с WinAVR 🙂
Так очень распространены примеры программ на ассемблере, у него куча преимуществ но один значительный недостаток, при изучении с нуля необходимо присутствие специалиста. Так же в интернете есть еще куча различных вариантов для написания управляющей программы микроконтроллера такие как: Basic, Flow Code и тд. тп. Но как-то так сложилось, как в своё время с операционной системой Windows, она не самая лучшая но никому нет до этого дела. Кстати о Windows советую посмотреть фильм “Пираты силиконовой долины”.

И так, прерывание – это событие, после которого выполняется подпрограмма (обработчик прерываний).
Пример:
Основная программа – бегущий огонек из 8-ми светодиодов, выполняется при включении питания микроконтроллера.
К микроконтроллеру подключена кнопка к ножке int0 (смотри datasheet на конкретный микроконтроллер).

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

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

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

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

  1. SIGNAL (SIG_INTERRUPT1)//программа-обработчик прерываний

  2. {

  3. }

  4. int main (void)//основная программа

  5. {

  6. sei(); //Разрешаем глобально прерывания

  7. }

Так же предусмотрен вариант, когда необходимо на время запретить выполнение прерываний, для это используют следующую запись в языке C:

  1. {

  2. cli(); //запрещаем выполнение прерываний

  3. }

Вызвать прерывание можно несколькими способами, верней даже это не способы а причины появления прерывания. В datasheet имеется таблица прерывания микроконтроллера, и в ней можно посмотреть все источники этих самых прерываний.
Таблица прерываний микроконтроллера ATtiny2313:

Номер вектораАдрес подпрограммыИсточник прерыванияОписание прерывания
1 0x0000 RESET Внешний сброс, сброс при включении питания, сброс по срабатыванию охранного таймера
2 0x0001 INT0 Внешний запрос на прерывание по входу INT0
3 0x0002 INT1 Внешний запрос на прерывание по входу INT1
4 0x0003 TIMER1 САРТ Прерывание по захвату таймера/счетчика 1
5 0x0004 TIMER1 COMPA Прерывание по совпадению таймера/счетчика 1. Канал А
6 0x0005 T1MER1 OVF Прерывание по переполнению таймера/счетчика 1
7 0x0006 TIMER0 OVF Прерывание по переполнению таймера/счетчика 0
8 0x0007 USART0, RX USART0, прием завершен
9 0x0008 USART0, UDRE USART0 буфер данных пуст
10 0x0009 USART0, TX USART0, передача завершена
11 0x000A ANALOG COMP Прерывание от аналогового компаратора
12 0x000B PCINT Прерывание по изменению на любом из выводов
13 0x000C TIMER1 COMPB Прерывание по совпадению таймера/счетчика 1. Канал В
14 0x000D TIMER0 COMPA Прерывание по совпадению таймера/счетчика 0. Канал В
15 0x000E TIMER0 COMPB Прерывание по совпадению таймера/счетчика 0. Канал А
16 0x000F USI START Прерывание по USI. Готовность к старту
17 0x0010 USI OVERFLOW Прерывание по USI, Переполнение
18 0x0011 ЕЕ READY Готовность EEPROM
19 0x0012 WDT OVERFLOW Переполнение охранного таймера

Пример синтаксиса объявления подпрограммы-обработчика прерываний:
Обработка прерываний INT1

  1. SIGNAL (SIG_INTERRUPT1)

  2. {

  3. }

Обработка прерываний по переполнению таймера 0

  1. ISR (TIMER0_OVF_vect)

  2. {

  3. …<\p>

  4. }

Обработка прерывания по приходу байта на модуль USART

  1. ISR(USART_RXC_vect)<\p>

  2. {

  3. }

  1. SIGNAL (SIG_OVERFLOW0)

  2. {

  3. }

Синтаксис для компилятора WinAVR в CV-AVR немножко по другому будет выглядеть, обратите внимание.

https://www.youtube.com/watch?v=LmGGwtT3-i4

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

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

»

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

Урок 5. Аппаратные прерывания

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

Прерывания

    Когда устройство готово оно инициирует прерывание. В большинстве случаев схема с прерываниями значительно лучше. Каждый раз нажимая клавишу на клавиатуре вы инициируете прерывание. Аналогично аппаратный таймер инициирует прерывание через заданные промежутки времени, например каждые 50 мс. На низком уровне прерывания от внешних устройств инициируются следующим образом. Все устройства подсоединяются к программируемому контроллеру прерываний (programmable interrupt controller, PIC). PIC, в свою очередь, подсоединен к линии прерываний центрального процессора. Он используется как мультиплексор, а также может назначать приоритет прерываниям. На самом деле это и есть несколько усовершенствованный мультиплексор 8 к 1. Однако, кто-то где-то решил, что 8 линий для аппаратных прерываний не достаточно, и они присоединили к первому контроллеру второй такой же контроллер прерываний. Так что во всех современных ПК находится два контроллера прерываний: master и slave, обслуживающих в общем счете 15 различных устройств (одна линия master контроллера используется для подключения slave контроллера). Когда компьютер загружается, таблица прерываний в PIC выглядит следующим образом:

  • IRQ 0..7 – INT 0x08..0x0F;
  • IRQ 8..15 – INT 0x70..0x77.

То есть прерывания от master контроллера конфликтуют с номерами прерываний, используемых процессором (смотри последний урок). Нам надо будет переназначить номера прерываний. Обычно IRQ 0..15 назначают на ISR 32..47 (31 – номер последнего прерываний, используемого процессором).
Обмен сообщениями с PIC осуществляется с помощью шины ввода-вывода. Каждый контроллер прерываний имеет свой порт данных и командный порт:

  • Master – команды: 0x20, данные: 0x21
  • Slave – команды: 0xA0, данные: 0xA1

Код для назначения новых номеров прерываний достаточно сложен для понимания. Если вас интересует что же происходит на самом деле – здесь есть замечательное объяснение. // Remap the IRQ table // Send initialization signal outb(0x20,0x11); outb(0xA0,0x11); // Set offset outb(0x21,0x20); outb(0xA1,0x28); // Set master-slave relation outb(0x21,0x04); outb(0xA1,0x02); // Set 8086 mode outb(0x21,0x01); outb(0x21,0x01); // End of mess outb(0x21,0x00); outb(0xA1,0x00); … idt_set_gate( 32, (u32int)irq0, 0x08, 0x8E); … idt_set_gate( 47, (u32int)irq15, 0x08, 0x8E);
Теперь мы должны установить обработчики для прерываний 32-47. Также мы должны добавить для них единый обработчик.%macro IRQ 2
[GLOBAL irq%1]
irq%1: cli push byte 0 push byte %2 jmp irq_common_stub
%endmacro … IRQ 0, 32
IRQ 1, 33

IRQ 15, 47
Мы не можем использовать единый обработчик, с помощью которого мы обрабатывали прерывания до сих пор, потому что в этом обработчике мы должны послать в PIC сигнал EOI (End of Interrupt). Причем, если запрос на прерывание пришел от master контроллера, то мы должны послать сигнал EOI ему, если же от slave контроллера – то и в master и в slave.[EXTERN irq_handler] irq_common_stub: pusha ; проталкивает в стек значение из edi,esi,ebp,esp,ebx,edx,ecx,eax mov ax, ds ; младшие 16 бит eax = ds push eax ; сохраняем регистр сегмента данных mov ax, 0x10 ; загружаем смещение для сегмента данных ядра mov ds, ax mov es, ax mov fs, ax mov gs, ax call irq_handler pop ebx ; возвращаем оригинальное значение сегмента данных mov ds, bx mov es, bx mov fs, bx mov gs, bx popa add esp,8 ; очищаем стек от значений кода ошибки и номера вектора прерывания sti iret
И код на C:void irq_handler(registers_t regs)
{ // Посылаем контроллеру прерываний сигнал EOI (end of interrupt) // если прерываний пришло от второго контроллера (slave) if (regs.int_no >= 40) { // Посылаем сигнал reset второму контроллеру (slave) outb(0xA0,0x20); } // первому контроллеру (master) посылаем сигнал EOI в любом случае outb(0x20,0x20); if(interrupt_handlers[regs.int_no] != 0) { isr_t handler = interrupt_handlers[regs.int_no]; handler(regs); }
}
Тут все просто. Если номер прерывания > 40 (IRQ > 7), то мы посылаем сигнал EOI в slave контроллер. В master контроллер мы посылаем сигнал EOI в любом случае. Также, Вы наверное заметили, что я добавил простой механизм, позволяющий регистрировать и вызывать обработчики прерываний. Нам понадобятся еще несколько объявлений:#define IRQ0 32

#define IRQ15 47 typedef void (*isr_t)(registers_t);
void register_interrupt_handler(u8int n, isr_t handler);

isr_t interrupt_handlers[256]; void register_interrupt_handler(u8int n, isr_t handler)
{ interrupt_handlers[n] = handler;
}
Вот и все. Теперь мы можем обрабатывать прерывания от периферийных устройств. Все что нам теперь нужно – это проверка нашего механизма на конкретном примере. Программируемый таймер – это чип, подключенный к линии IRQ0. Он может генерировать прерывание с заранее заданной частотой (между 18,2 Гц и 1.1931 МГц). Прерывания от таймера используют системные часы ОС, а также он используется для организации многозадачности. Таймер оснащен встроенным осциллятором, частота которого около 1.1931 МГц. Сигнал от него проходит через делитель частоты для модуляции выходной частоты. У таймера три канала:

  • Канал 0. Его выход подключен к линии IRQ0;
  • Канал 1 ранее использовался для очистки DRAM. Современные ОС его не используют;
  • Канал 2 контроллирует динамик ПК.

Единственный канал. используемый ОС – это канал 0. Если мы хотим, чтобы таймер генерировал прерывание через заданные промежутки времени, мы должны сообщить делителю частоты значение N:freq = 1193180 Hz / N;
Для таймера задано четыре порта ввода-вывода: 0x40-0x42 – порты данных, 0x43 – командный. Нам понадобятся несколько новых файлов.

timer.h:

#ifndef TIMER_H_
#define TIMER_H_ #include “common.h” void init_timer(u32int frequency); #endif

timer.c:
#include “timer.h”
#include “isr.h”
#include “monitor.h” static u32int tick = 0; static void timer_callback(registers_t regs)
{ tick++; monitor_write(“Tick: “); monitor_write_dec(tick); monitor_write(”
“);
} void init_timer(u32int freq)
{ // Для начала регистрируем наш callback register_interrupt_handler(IRQ0,&timer_callback); // Значение, сообщаемое в PIT u32int divisor = 1193180 / freq; // Послать команду outb(0x43,0x36); // Значение посылается в два этапа u8int l = (u8int)(divisor & 0xFF); u8int h = (u8int)((divisor>>8) & 0xFF); monitor_write(“Low: “); monitor_write_dec(l); monitor_write(” High: “); monitor_write_dec(h); monitor_put('
'); // Посылаем на порт данных outb(0x40,l); outb(0x40,h);
}

Давайте пройдемся по коду. Во-первых у нас есть функция init_timer(), которая “навешивает” функцию timer_callback() в качестве обработчика на перывание IRQ0. Она будет вызвана как только произойдет прерывание. Затем мы вычисляем значение для делителя частоты и посылаем его в два этапа. Байт 0x36 посланный на командный порт таймера во-первых переводит его в режим повторения (когда необходимо генерировать исключение многократно через равные промежутки времени) и сообщает делителю частоты, что на порт данных должно поступить значение N.

Когда это сделано, просто надо добавить в нашу функцию main() следующий код:

init_timer(50);
Компилируем и проверяем результат. Пример работы программы:
Именно. Программа просто каждый раз при прерывании от таймера увеличивает счетчик тиков на 1 и выводит его значение на экран.

На этом все. А в следующий раз мы займемся организацией виртуальной памяти.

Код уже на github.

Также код можно скачать по ссылке: http://dl.dropbox.com/u/40211944/Lesson5.tar.gz

Источник: http://gownos.blogspot.com/2011/10/5.html

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