Прерывания: есть вопросы - нужны ответы
- Войдите на сайт для отправки комментариев
Ср, 07/10/2015 - 19:43
Всем привет!
Вопрос первый. Есть программа, в которой loop() выполняется долго. И есть кнопка, обрабатываемая по прерыванию. После обработки прерывания программа возвращается в то место, где ее "отвлекли", и продолжает выполняться дальше. А может есть возможность сделать возврат из прерывания в другое место, например, на начало loop()? Спасибо.
Возможность такая есть, но это "сильно лезть под юбку системе". Это не для чайников - это "высшая лига". Вы программист "высшей лиги"? Если нет - забудьте и считайте, что такой возможности нет. Если же Вы считаете себя таковым, то пожалуйста: - достаточно на стеке подменить адрес возврата после прерывания на любой адрес, который Вам нравится, например, на адрес функции loop. Тогда команда RETI перейдёт туда, куда Вам надо. Правда, стэк при этом Вы должны почистить сами (от возможных адресов возврата других вызываемых функций).
Нет, я не программист "высшей лиги", но принцип я понял. Спасибо!
Вопрос второй. Та же программа, та же кнопка.
Чтобы не засиживаться долго в функции обработки прерывания, в ней самой только выставляем флаг, что кнопку нажали и возвращаемся в программу. Но чтобы не было так скучно, кнопку хочется "озвучить". Для этого имеется пищалка (со встроенным генератором) и ее необходимо включить на короткое время. И хоть время короткое, но паузу нужно выдержать, а внутри обработчика прерывания ни delay(), ни millis() не работают. Крутить пустые циклы...? Как-то не то. И хоть такая конструкция:
работает (времени на обработку процессором "бесполезного" delay достаточно для срабатывания пищалки), может все-таки есть более цивилизованные способы сделать задержку в функции обработки прерывания?
Чтобы не засиживаться долго в функции обработки прерывания, в ней самой только выставляем флаг, что кнопку нажали и возвращаемся в программу.
Это очень грамотно и правильно.
И хоть время короткое, но паузу нужно выдержать, а внутри обработчика прерывания ни delay(), ни millis() не работают. Крутить пустые циклы...? Как-то не то. И хоть такая конструкция:
digitalWrite(pinBeeper, HIGH);
delay(1);
digitalWrite(pinBeeper, LOW);
работает (времени на обработку процессором "бесполезного" delay достаточно для срабатывания пищалки), может все-таки есть более цивилизованные способы сделать задержку в функции обработки прерывания?
А вот здесь Вы противоречите своему же грамотному подходу. Правильно делать озвучку не в функции-обработчике прерывания, а снаружи, также, как Вы писали в начале – по взведённому флагу. Чем быстрее выполняется обработчик прерывания. Тем лучше.
Я отвечу на Ваш вопрос (как это сделать в обработчике прерываний), но, повторяю, это плохая идея, лучше делайте так, как Вы описали в начале – выставляйте флаг, а пищите уже вне обработчика по флагу.
Итак, внутри обработчика:
Во первых, не надо бояться цикла. Не боитесь же Вы delay()? А это тоже мёртвый цикл в котором только yield() вызывается.
Во-вторых, внутри обработчика Вы можете разрешить посторонние прерывания (функция sei()). Если мне не изменяет память, этого достаточно, чтобы заработала millis() (а тогда и delay заработает). Но если вдруг не заработает, то нужно ещё сбросить флаг «нахожусь в обработке прерывания». Сброс флага в общем случае – прописать 1 в определённый бит. Какой бит – зависит от прерывания.
vk007, возьмите пищалку без генератора. В функции tone() можно установить длительность сигнала, а заодно и частоту. Важно заметить, что фактически tone() работает "в фоне", типа многозадачность)
Я отвечу на Ваш вопрос (как это сделать в обработчике прерываний), но, повторяю, это плохая идея, лучше делайте так, как Вы описали в начале – выставляйте флаг, а пищите уже вне обработчика по флагу.
Не. Он не сможет в основном цикле писчать. Основной цикл медленный (что и есть первоисточник бед), писчать начнет слишком позно после нажатия.
Тут надо так: в обработчике прерывания кнопки взводим флаг для действия в основном цикле, начинаем писчать и настраиваем аппаратный таймер на заданую длительность, когда таймер досчитает, то в прерывании таймера выключаем звук. Можно немного упростить если повезет - настроить таймер на прямую работу с пином и сигнал с этого пина разрешает писк.
Logik, человек же прямо сказал, что он "не из высшей лиги". А то, что Вы описали - не так тривиально. Медленный цикл не помеха - он же "одним делэем" медленный, так что можно ставить проверку флага не в одном месте, а ... хоть через строчку и пищать себе на здоровье вовремя.
tone() я уже пробовал, но она отъедает пару килобайт кода и будет пищать только после выхода из функции обработки прерывания, вобщем, не нравится она мне )
А что касается "пищать в основной программе", я уже упоминал - loop длинный, пока доберемся до места проверки флага, то и пищать уже не к месту. Логично "нажали кнопку" - "пискнули".
Кстати, отсюда еще один вопрос, как ловить состояние флага не дожидаясь полного цикла loop? Разбросать по всей программе проверки? Бррр... Таймер? Но я пока с ними не дружу и возможностей их не знаю.
vk007, тогда можно таймером подать команду, тоже "многозадачность" , как отпищит сам отключится :)
Прерывание таймера - не "высшая лига" ни разу, это "кубок области" не выше. От прямая коррекция стека - да сложно.
Если делей один - убрать его к праотцам, методика проста. Тока я не вижу где у ТС указно что он один в лупе. Проверка флага чаще не поможет, т.к. прерывание именно в середине делея будет как правило.
А что касается "пищать в основной программе", я уже упоминал - loop длинный, пока доберемся до места проверки флага, то и пищать уже не к месту.
Я ж и пишу что длинный луп - первопричина всех бед. Может проще его сделать быстрым, так до 50мс? Тогда и проверку нажатия и старт-стоп писков, все в нем и без прерываний.
Бррр... Таймер? Но я пока с ними не дружу и возможностей их не знаю.
Не бррр, а ням-ням. Просто раз надо научится "готовить". Примеров море.
Разбросать по всей программе проверки? Бррр...
Нечего брызжать. Чтобы не выглядело ужасно, определите макрос. Например,
#define PIS if (pisk_flag) { pisk_flag = false; pischalka(); }
А потом хоть через строчку просто пишете
...
PIS
...
PIS
...
Не так уж она и испохабила код.
Ну, разумеется, функцию pischalka() надо написать. В начале скетча где-нибудь её расположите.
Не бррр, а ням-ням. Просто раз надо научится "готовить". Примеров море.
:) бррр касалось не таймеров, а разбросать по программе проверки флага
Мне больше понравился ответ о стеке. Всех зайцев отловить можно, в т.ч. и пропищать, и флаг проверить.
Всем спасибо за проявленное внимание, а я полезу-ка неспешно в интернеты освежать и набираться знаний о стеке и его особенностях в отношении к AVR.
Вот так вот. В "высшую лигу" минуя "кубок области"?! Ну, бог - помощ :)
ПС. Не забывайте отписатся о ходе и результате.
Посмотрите заодно про вполне стандартные сишные функции setjmp и longjmp. С их помощью можно решить задачу "выпрыгивания" в определённое место программы из любой точки любой функции. При выпрыгивании из обработчика прерываний будут, по сравнению с обычной функцией, некоторые отличия, но незначительные. Скорее всего, достаточно будет "вручную" разрешить прерывания.
В принципе, вникать при этом во все тонкости работы со стеком совсем не обязательно.
Но, конечно, всё-таки желательно. :)
Вот так вот. В "высшую лигу" минуя "кубок области"?! Ну, бог - помощ :)
ПС. Не забывайте отписатся о ходе и результате.
Да ладно :) Для меня ассемблер (а я понимаю, что без ассемблерной вставки не обойтись) не самый темный лес. Вот как раз пытаюсь в протеусе осмыслить дизассемблированный код простенького скетча. Но все это было очень давно и... Ну в общем освежать в памяти много чего надо.
А отписываться о ходе, то это врядли. Все это будет проходить ооочень неспешно и не факт, что проблема не решится каким-нибудь другим способом.
Ну что же, вопрос номер два решился: не работает в прерывании delay() зато заработала _delay_ms().
Посмотрите заодно про вполне стандартные сишные функции setjmp и longjmp. С их помощью можно решить задачу "выпрыгивания" в определённое место программы из любой точки любой функции. При выпрыгивании из обработчика прерываний будут, по сравнению с обычной функцией, некоторые отличия, но незначительные. Скорее всего, достаточно будет "вручную" разрешить прерывания.
В принципе, вникать при этом во все тонкости работы со стеком совсем не обязательно.
Но, конечно, всё-таки желательно. :)
Оптимально выходить честным reti но не на loop(), а на чинилку стека. А чинилка - функция из одной строчки "longjump" на заранее определённую setjump'oм метку в начале loop. Так должно работать.
Это понятно, да - чем глубже лезешь, тем меньше совместимости.
Насчёт бита прерывания - я примерно о нём и говорил, что надо разрешить прерывания "вручную".
Ещё могут быть определённые биты регистров контроллера прерываний - но это уже действительно зависит от конкретного железа. В ATmega'х, насколько я помню, они сбрасываются сами, уже при входе в обработчик.
А в общем, пробовать надо - так, на словах, что-нибудь да забудешь, обязательно. :)
vk007,
пример, как выйти из прерывания в начало функции loop() есть в теме http://arduino.ru/forum/programmirovanie/ch-2-st-119-uk-rf-1960-g
Спасибо, ознакомился.
Вот набросал на скорую руку:
Так можно использовать setjmp/longjmp, оно будет работать или я намутил что-то бредовое?
Ну, вообще-то критерий истины - попробовать. На глаз это не должно стабильно работать.
Дело в том, что btnClick вызывается в контексте обработчика прерываний, а значит это не обычная функция. Для корректного выхода из неё нужно, как минимум, установить в логическую единицу бит 7 регистра SREG, иначе никакое другое прерывание не сможет обработаться (вложенные прерывния по умолчанию запрещены). Также, в зависимости от конкретного прерывания, возможны другие необходимые действия. Так же как Вы делаете, управление переходит куда Вам надо, но контроллер всё равно остаётся в режиме обработки прерывания.
Ну, вообще-то критерий истины - попробовать.
А вот тут проблема. Скетч компилится, но заливаться не хочет.
avrdude: stk500v2_ReceiveMessage(): timeout
avrdude: stk500v2_getsync(): timeout communicating with programmer
При этом другие скетчи льются нормально.
Видимо пока от этой идеи придется отказаться и понатыкивать по всей программе проверку флага с goto на начало.
Вся беда в длинном loop() как уже прозвучало. Сильно подозреваю, что в нем понатыкано delay() везде где можно. Гуглите "автоматное программирование" и вот эту тему: http://arduino.ru/forum/programmirovanie/novichkam-avtomatnoe-programmir...
и будет вам щастье.
Вот кстати... Сам про это писал, а теперь засомневался. Вполне возможно, этот SREG тоже сохраняется в контексте setjmp'а. Если да - может и не придётся больше ничего сохранять.
Короче, надо всё уточнять и пробовать - а мне сейчас пока не на чем.
Да это понятно. Но у нас тут уже чисто спортивный интерес - написать криво, но при этом чтоб работало. :)
Чтобы не засиживаться долго в функции обработки прерывания, в ней самой только выставляем флаг, что кнопку нажали и возвращаемся в программу. Но чтобы не было так скучно, кнопку хочется "озвучить". Для этого имеется пищалка (со встроенным генератором) и ее необходимо включить на короткое время. И хоть время короткое, но паузу нужно выдержать, а внутри обработчика прерывания ни delay(), ни millis() не работают. Крутить пустые циклы...? Как-то не то.
не проще ли перейти к железу ?
в обработчике нажатия выставить 1 на 1 миллисекунду на какой-то пин и сбросить.... ( запуск 555 )
на тот пин повесить таймер 555 с пищалкой .....
1 миллисекунда в обработчике прерывания .. это "потеря" около 16000 (шестнадцать тысяч!) команд для 16Мгц Меги .. или 8000 неисполненных команд для 8-Мгц ардуины.. не "многовато" за решение? :)
Попробую тут спросить, как в наиболее подходящей теме.
Добрый день всем! Я пока только начинаю позновать азы, посему прошу сильно не пинать. Пытаюсь для авто написать простенькую прогу по автозапиранию дверей. В ходе разработки потребовалось четкое отслеживание открытости дверей. Решил отслеживать это по прерыванию, а не простом считыванием сигнала.
Имеется андурино НАНО с двумя портами прерывания. Один порт занят под датчик скорости (считывания частоты), по второму порту нужно отслеживать положение дверей. Т.е. нужно точно знать, что был переход в состояние HIGH, и нужно точно знать что был переход в состаяние LOW. Попробовал сначала на один и тот же порт посадить две функции прерывания, но по разным фронтам (RISING и FALLING) - не заработало. Код прилагается:
Тогда написал код с прерыванием по смене фронта (CHANGE). По хорошему достаточно при смене фронта просто инвертировать нужный флаг, тогда при RISING -> 1, а при FALLING -> 0, но боюсь что может произойти сбой по какой либо причине, и флаг перескочит через фронт и будет инвертироваться в обратной последовательности RISING -> 0 и FALLING -> 1. Посему код решил написать так:
Этот код заработал нормально. Теперь собстенно вопрос. На сколько с точки зрения програмирования коряво написан код? Меня смущает что по одному и тому же пину идет прерывание и считывание. Или это абсолютно нормально?
Я конечно, не профессиональный программист- человек зарабатывающий на программировании, но выскажу мнение о аппаратном прерывании. Вот ответьте о месте рукопашного боя в современной войне. Ну да там где боец прое**л все и встрелился с таким же уникумом с таким же материальным обеспечением. Когда-то давно процессоры были медлеными, место под программы было мало. Не было средств эти программы писать тоже не было. Тогда использовать аппаратное прерывание было оправдано. Ну или наблюдать процессы соизмеримые с частотой процессора. Конечно, если у вас на машине стоит газовая турбина, и надо точно отслеживать скорость этой турбины. А двери на машине открываются с частотой дверей во время распродажи под скидки перед Новым Годом в единственном супермаркете в радиусе 10 км в центре города миллионика. Тогда да вы поступили правильно.
qwone
Мысли понята, принята и осознана. Не буду завязываться с прерыванием по этому каналу.
Но вопрос был немного в другом: насколько коряво написан сам код с точки зрения программирования? Хотя это уже не важно.
Но вопрос был немного в другом: насколько коряво написан сам код с точки зрения программирования? Хотя это уже не важно.
А вот вы и сами себе ответите. Вот мой скетч. Может вам пригодится в будущем.
Для работы надо разместить все файлы. Название в первой строчке.
П.С. Формально вход с концевиков у меня один, т.к. опятьже аппаратно все концевики дверей для сигналки объеденены в один через диодную развязку.
П.П.С. Пока у меня вырисовывался такой код:
Задержка в 50 мс в каждом цикле для имитирования уменьшения тактовой частоты до 20гц и меньшей нагрузки на процессор и стабилизатор напряжения (как мне кажется)
Т.к. это все итак будет греться до 50°С т.к. размещение планируется под торпедой.
П.С. Формально вход с концевиков у меня один, т.к. опятьже аппаратно все концевики дверей для сигналки объеденены в один через диодную развязку.
http://greenoakst.blogspot.com/2012/06/arduino.html
А об этом не знали?
Ну и мое добавление https://yadi.sk/d/7Uj4xEn038zpqk Код выложу постом ниже.
А об этом не знали?
Или я не понял Вашу мысль?