Ч. 2 ст. 119 УК РФ 1960 г.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

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

Итак, встречайте

Blink для извращен... нетрадиционных ардуинщиков

Не так давно один из коллег спросил:

vk007 пишет:

Есть программа, в которой 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), но я делать этого не стал т.к. шутка и так работает, а использовать это серьёзно я не советую никому.

Logik
Offline
Зарегистрирован: 05.08.2014

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

Olm
Offline
Зарегистрирован: 09.10.2014

Перед выполнением функций из loop запрещать прерывания а по выходу разрешать? или все функции в главный цикл поместить, и использовать только глобальные переменные volatile?

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Ну... примерно так пишется большинство диспетчеров мультизадачных сред. Разве что с ручным сохранением/восстановлением контекста.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Logik пишет:

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

Да ладно, в конце поста написано как с этим бороться - setjump сохранит указатель, а longjump восстановит. Просто для данного примера не нужно было.

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

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

Maverik
Offline
Зарегистрирован: 12.09.2012

самое большое извращение с функцией loop() я видел на этом форуме, и выглядело оно как-то так

void loop()
{
a:

...
здесь стотыщпицот строк быдлокода
...

goto a;
}