Ч. 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() я видел на этом форуме, и выглядело оно как-то так
void loop() { a: ... здесь стотыщпицот строк быдлокода ... goto a; }