Ч. 2 ст. 119 УК РФ 1960 г.
- Войдите на сайт для отправки комментариев
Указанная в заголовке статья предусматривала срок до 6 лет за извращения. Слава Богу, тот кодекс уже не действует, иначе я бы не решился публиквоать нижеследующий скетч.
Итак, встречайте
Blink для извращен... нетрадиционных ардуинщиков
Не так давно один из коллег спросил:
Есть программа, в которой loop() выполняется долго. И есть кнопка, обрабатываемая по прерыванию. После обработки прерывания программа возвращается в то место, где ее "отвлекли", и продолжает выполняться дальше. А может есть возможность сделать возврат из прерывания в другое место, например, на начало loop()?
Вопрос подвиг меня на написание самого извращённого блинка их тех, что я видел.
Итак, в самом начале функции loop() инверитруется состояние светодиода, а затем функция проваливается в бесконечный цикл и, таким образом, исполняется ОЧЕНЬ долго.
При этом раз в секунду тикает таймер, вызывая прерывание, а вот из прерывания возврат осуществляется не в точку, где была прервана программа, а прямо в начало функции loop() (как и просил уважаемый коллега). Та, разумеется, инверитует состояние пина и снова проваливается в бесконечный цикл. Через секунду случается новое прерывание и всё повторяется.
В итоге получается горячо любимый blink() - каждую секунду светодиод меняет своё состояние. И при этом в loop() присутсвует глухой, бесконечный цикл.
#define INTERVAL 49911u // Значение счётчика, чтобы переполнение //случилось через 1 секунду int LoopAddress = (int) loop; // Здесь хранится адрес loop uint8_t SaveR10; /* * Обработчик прерывания. Выполняется 1 раз в секунду. */ ISR(TIMER1_OVF_vect) { TCNT1 = INTERVAL; // Восстановим счётчик таймера для следующего переполнения __asm__( "sts SaveR10, R10" "\n" // Сохраним регистр R10 от греха подальше "pop R10" "\n" // Подменим адрес возврата на стеке на адрес функции loop() "pop R10" "\n" "lds R10, LoopAddress" "\n" "push R10" "\n" "lds R10, LoopAddress+1" "\n" "push R10" "\n" "lds R10, SaveR10" "\n" // Восстановим регистр R10 "reti" "\n" // Выходим ); } void SetupTimer() { cli(); // Запретим прерывания, чтобы никто не мешал PRR &= (unsigned)~(1 << PRTIM1); // Убедимся, что таймер 1 не отключен TIMSK1 |= (unsigned)(1 << TOIE1); // Разрешим прерывание по переполнению TCCR1B = (unsigned)((1 << CS12) | (1 << CS10)); // Установим делитель частоты 1024 TCCR1A = 0u; // Установим Normal режим TCNT1 = INTERVAL; // Установим счётчик, теперь переполнение случится через 1 секунду. TIFR1 = 1; // Сбросим прерывание по переполнению на таймере 1 sei(); // Разрешим прерывания } /* * setup как setup – ставит режим пина и устанавливает таймер */ void setup() { pinMode(LED_BUILTIN, OUTPUT); SetupTimer(); } /* * loop * инвертирует состояние пина и проваливается в совершенно мёртвый, бесконечный цикл */ void loop() { digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // Переключаем состояние пина while(true); // Уходим в мёртвый цикл }
ПРЕДУПРЖДЕНИЕ: эта программа-шутка. Не надо использовать этот код в проектах.
Мало того, что такой изврат в принципе нежелателен, да ещё здесь есть одно упрощение - всё хорошо работает только если прерывание случилось в самой функции loop(), а не, например, в другой, вызванной из loop функции. Чтобы работало всегда, надо восстанвливать стек (проще всего это сделать при помощи setjump/longjump), но я делать этого не стал т.к. шутка и так работает, а использовать это серьёзно я не советую никому.
Не все так просто. В общем случае такой подход приведет к засорению стека и краху системы, т.к. при прерывании не из уровня Loop а из некоторой функции, то в стеке будет оставатся адрес возврата из этой функции и её локальные переменные. В обработчике прерывания не известно будет с какого уровня вложенности прервались и сколько лишнего в стеке и соответственно скорректировать не выйдет. Надо бы сохранять при первом входев Loop указатель стека и в обработчике перед сохранением адреса возврата устанавливать указатель в сохраненное значение. Но и это не решит всех проблем... Думаю интересней выходить не в начало Loop а в его самый конец, пусть он типа корректно завершится и система сама его перезапустит.
Перед выполнением функций из loop запрещать прерывания а по выходу разрешать? или все функции в главный цикл поместить, и использовать только глобальные переменные volatile?
Ну... примерно так пишется большинство диспетчеров мультизадачных сред. Разве что с ручным сохранением/восстановлением контекста.
Не все так просто. В общем случае такой подход приведет к засорению стека и краху системы, т.к. при прерывании не из уровня Loop а из некоторой функции, то в стеке будет оставатся адрес возврата из этой функции и её локальные переменные. ... Надо бы сохранять при первом входев Loop указатель стека
В сетапе запретить прерывания. В лупе в начале первого цикла запомнить глубину стека. И всего делов то.
самое большое извращение с функцией loop() я видел на этом форуме, и выглядело оно как-то так