Использование SPI в основной программе и прерывании одновременно

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

При работе над проектом http://arduino.ru/forum/proekty/analog-analogovogo-sintezatora#comment-261101

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

Но в проекте уже есть несколько устройств, управляемые по SPI.

Сложность в том, что нужно:

- читать сигнал с АЦП по SPI на частоте 2 МГц из цикла loop(),

- передавать данные в DSP VS1053B по SPI на частоте 1 МГц из цикла loop(),

- задавать режим цифровых потенциометров по SPI на частоте 4 МГц из прерывания по таймеру с частотой 3 кГц или выше.

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

Первая мысль - обернуть цикл взаимодействия по SPI в loop() командами запрещения и разрешения прерывания. Но не хотелось бы тормозить прерывание на десятки мкс пока закончится этот цикл взаимодействия.

Клапауций 823
Клапауций 823 аватар
Offline
Зарегистрирован: 13.01.2017

я запретил.

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

Думаю можна работать не задумываясь о проблеме. Дело в том, что вхождение в прерывание довольно медленое, пока все регистры посохраняются, то и SPI доработает. По крайней мере байт на 2МГц выпихнет. Если же скорость ниже и/или данных больше - можна делать предпроверку освобождения шины с ожиданием его. И просто запретом прерывания тут не обходится, необходимо еще и помнить о варианте пихонули данные из прерывания, завершили его, и в лупе сразу попали снова на передачу. Но опять таки на 4МГц SPI доработает быстрей, чем выйдем из прерывания.

В общем писал бы без учета проблемы, но старался бы обработчик прерывания делать так, чтоб непосредственно работа с шиной была ближе к его концу а в начале всякое вспомогательное, чтоб дать больше времени на завершение отправки на 1-2Мгц, а на 4МГц и так успеет. Дальше глянул бы дизассемблер что вышло, прикинул бы время и протестировал с пристрастием. При появлении проблемы - предпроверка с циклами ожидания в обработчик прерывания. Запрет прерывания тоже бы сделал но небольшой, сколько там по логике функции отправки получилось бы, ну допустим 2-3мксек. В общем бы и получилось что в худшем случае (прерывание сразу после отправки на 1МГц) прерывание подзадержалось бы на пару мксек, еще пару мксек входило бы в него еще несколько делало бы вспомогательные действия  в прерываниии и на крайняк чуть-чуть подождало бы, что случалось бы крайне редко.

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

andriano,

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

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Logic, что-то я немного не понял логики работы.

Вот, скажем, я передаю данные в loop() порциями по 6 байт с частотой 1 МГц (что по времени существенно больше, чем время входа в прерывание), а из прерывания - порциями по 4 байта на частоте 4 МГц.

Скажем, я в loop() установил скорость передачи в 1 МГц и начал передавать данные. Случилось прерывание. Если у меня нет запрета на прерывания, то loop() будет прерван посередине, частота переустановлена в 4 МГц, будут переданы данные и произойдет возврат из прерывания, после чего передача оставшегося в loop() должна продолжиться уже на 4 МГц, т.к. в 1 МГц ее никто не перевел.

Кроме того, сигнал CS для 1-МГц устройства на время прерывания тоже никто не отключил, поэтому это устройство честно попытается прочитать с шины то, что предназначено другому устройству.

Я правильно рассуждаю или нет?

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

Правильно. Это я исходил из того, что вы передаете один байт, ктож знал что их аж 6 будет? ))  Тогда написаное мной относится только к последнему байту. Если 6 байт - то конечно запрещать прерывание. Можна не все, а только SPI-ное ну или таймера из которого SPI трогаете.

ПС. Хотя можна еще устроить очередь. Т.е. если прерывание таймера произошло во время когда занята шина, то только взводим флаг, а по завершению обмена в лупе  проверяем этот флаг и делаем работу таймера по SPI. А если луп достаточно бытрый, то можна вобще забить на таймер по микрос следить за временем и задача сведется к предыдущей ранее решенной)))

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

В прерывании только ставите нужные данные в очередь, передачу, при наличии данных в очереди, делаете из loop().

Это единственно грамотное решение, остальное - костыли.

Волшебник
Offline
Зарегистрирован: 22.12.2016

wdrakula пишет:

В прерывании только ставите нужные данные в очередь, передачу, при наличии данных в очереди, делаете из loop().

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

Цифровые поты, где-то читал,  трещат, надо при переходе через ноль менять установки

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

ЕвгенийП, честно говоря, не понял, какдолжны взаимодействовать между собой beginTransaction(), endTransaction() и usingInterrupt().

Нужны ли первые две при работе из прерывания?

ПОследняя вызывается один раз в setup() или в каждом прерывании вместо beginTransaction()?

Какой номер передавать в последнюю при использовании прерывания таймера?

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

По поводу параметра, если Вы откроете исходник библиотеки SPI, там есть вот такой комментарий как раз про таймер

  // If SPI is used from within an interrupt, this function registers
  // that interrupt with the SPI library, so beginTransaction() can
  // prevent conflicts.  The input interruptNumber is the number used
  // with attachInterrupt.  If SPI is used from a different interrupt
  // (eg, a timer), interruptNumber should be 255.
  static void usingInterrupt(uint8_t interruptNumber);

По поводу всего остального. Да, usingInterrupt вызывается один раз из сетапа. Я этого ни разу не делал. А вот всё остальнео делал, вот работающий пример (см. функцию update)

#include	<SPI.h>
#include	<limits.h>

#define	PIN_BLANK	A0
#define	PIN_XLAT		10
#define	PIN_GSCLK	9

//	Здесь сидят данные для каналов. Нам не нужно 12 бит на канал, достаточно одного
uint16_t	tlcData = 0;

//	Зажечь/погасить светодиод на канале
void setPin(int8_t channel, bool val) {
	if (val) tlcData |= ((uint16_t)1 << channel);
	else tlcData &= ~((uint16_t)1 << channel);
}

// записать данные каналов в TLC5940 и актуализировать их
void update() {
	SPI.beginTransaction(SPISettings(F_CPU/2, MSBFIRST, SPI_MODE0));
	uint8_t n = 0;
	for (int16_t triada = tlcData; n < 8; (triada <<= 2), n++) {
		SPI.transfer(highByte(triada) & 0x80);	// Нам всё равно, 
		SPI.transfer(0);								//	что туда писать. 
		SPI.transfer(highByte(triada) & 0x40);	// Важно только 0 или не 0
	}
	SPI.endTransaction();
	digitalWrite(PIN_GSCLK,LOW);	// готовимся
	digitalWrite(PIN_BLANK, HIGH);//	заканчиваем период
	digitalWrite(PIN_XLAT, HIGH);	//	защелкиваем данные
	digitalWrite(PIN_BLANK, LOW);	//	начинаем новый период
	digitalWrite(PIN_GSCLK,HIGH);	//	единственный тактирующий сигнал
	digitalWrite(PIN_XLAT, LOW);	//	уборка мусора
}	

//	Зажечь/погасить все светодиоды разом
inline void clearAll(void) { tlcData = 0; }
inline void setAll(void) { tlcData = UINT_MAX; }


void setup(void) {
	SPI.begin();
	pinMode(PIN_BLANK,OUTPUT);
	pinMode(PIN_XLAT,OUTPUT);
	pinMode(PIN_GSCLK,OUTPUT);
	digitalWrite(PIN_BLANK, HIGH);
	digitalWrite(PIN_XLAT, LOW);
}

void loop() {
	static int8_t channel = 0, dir = 1;
	clearAll();
	setPin(channel, HIGH);
	if (channel == 0) dir = 1;
	else if (channel == 15) dir = -1;
	channel += dir;
	update();
	delay(50);
}

 

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

wdrakula пишет:

В прерывании только ставите нужные данные в очередь, передачу, при наличии данных в очереди, делаете из loop().

Это единственно грамотное решение, остальное - костыли.

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

ПС. Глянул я на эти "транзакции" из SPI.cpp. Чуда не случилось, они просто запрещают  между begin и end. В общем см. первый пост темы )))

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

2Logic: Не, ну кто в XX! то веке передает по одному байту?!?

2ЕвгенийП: Спасибо, исходноки я, конечно, смотрел. ПРавде, видать, не очень внимательно - насчет номера 255 не отложилось. Ну так все равно непонятно: по сути мы лишь сообщаем системе, что в неизвестно каком прерывании будет использоватья SPI с неизвестно какими установками. В общем, понятность изложения в этом фрагменте я оцениваю как "ниже среднего". Поэтому и возникают вопросы.

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

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

2All: По поводу собрать всю работу с SPI в одно место: либо в прерывании, либо в loop(). Мне эта идея не нравится. Потому что по логике есть достаточно ограниченный объем работы, которую нужно делать вовремя и есть достаточно большой объем, которую нужно делать хотя бы когда-то. Переносить все в прерывание - нереально, просто прерывание не будет успевать сделать все, что нужно. А городить еще внутри прерывания диспетчер, который будет определять, какую часть работы сделать сейчас, а какую оставить на потом - тоже не хочется: прерывание не резиновое.

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

Собственно, на данный момент расклад по времени выглядит примерно так (проект: Аналог аналогового синтезатора):

- прерывание происходит по таймеру каждые 332 мкс (частота ~3кГц), в эти моменты времени вычисляются корректировки в характеристики звука: частота, амплитуда, добротность и резонанс фильтра, после чего эти корректировки сразу заносятся в управляемые устройства. Кроме того, в прерывании же организованы программные генераторы LFO (3шт) и ADSR (4шт). Среднее время выполнения прерывания 160 мкс, зафиксированный максимум 204 мкс. Это "внутреннее" время прерывания, т.е. не учитывает время вхождения в прерывание и выхода из него. В дальнейшем, вероятно, это время еще увеличится мкс на 50-60. Т.е. максимум вместе с временем вхождения может достичь ~275 мкс, оставляя на loop() лишь около 55 мкс из каждых 332. Впихнуть сюда еще дополнительно обмен по SPI, да еще с диспетчеризацией, мне кажется, нереально.

- цикл loop() занимает порядка 5-8 мс и включает опрос всех датчиков, среди которых 4 микросхемы АЦП, опрашиваемые по SPI (всего 32 аналоговых канала), 13 каналов встроенного АЦП Атмеги, 4 микросхемы входного сдвигового регистра для опроса 24 кнопрок, а также отслеживание изменений во всех этих показаниях и вывод их на экран по I2C. 

- в цикле loop() имеется высокоприоритетная операция - опрос MIDI-входа и реакция на него. Для оперативности вызовы проверки MIDI в количестве нескольких десятколв разбросаны внутри loop() так, чтобы задержка не превосходила ~300 мкс. Сейчас максимальная задержка составляет ~180 мкс + время отобранное прерыванием. Собственно, уже не очень укладываемся. Время реакции на приход MIDI-сообщения - до 70 мкс. Собственно, именно в эту "реакцию" я постарадся вынести максимум того, что было можно из прерывания.

Уже сейчас максимальная задержка может зашкаливать за 500 мкс: 180 + время двух прерываний. Думаю, для процесса с периодичностью 332 мкс задержка более длины периода - это слишком. Да и второй приоритетный процесс в loop() организовывать не хочтся.

Т.е. сгруппировать всю работу с SPI где-то в одном месте не получается. Но и по-другому - не очень: по сути, если мы запретим прерывание на время больше 55 мкс, у нас уже одно прерывание "наедет" на другое. В общем, куда ни крути, везде тесно.

2Волшебник: Собственно из за этого "треска" сейчас и думаю, как переделать схему. Пока использовался цифровой регулятор громкости M62429 с оригинальным способом управления (похоже на SPI, но без CS, поэтому SPI использовать нельзя). Шаг логарифмический (что хорошо) по 1 дБ (что плохо) и максимальная частота 0.25МГц (что тоже плохо). Собственно, 1дБ - это предел различимисти человеческого уха, но из-за изменения коэффициента передачи в произвольные моменты времени возникает помеха порядка -20дБ. Переход на цифровые потенциометры обещает даже без специальных усилий увеличить это отношение примерно до -50дБ. Ну и есть схемы, где отслеживается переход сигнала через 0, но для работы тех схем, что я видел, нужен сигнал CS - именно по нему происходит переключение коэффициента передачи. Т.е. после окончания передачи CS придерживается до тех пор, пока сигнал не пересечет 0.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Logik пишет:

В общем см. первый пост темы )))

Первый пост:

Клапауций 823 пишет:

я запретил.

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

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

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

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

andriano пишет:

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

Видимо, Вы изучали программирование в те же времена, что и я, а с тех с теорией компиляции жизнь Вас не сталкивала. То, что Вы говорите, было бы верно в 70-80-ые годы прошлого века, но с тех пор всё шагнуло далеко вперёд, а gcc - вполне приличный современный компилятор.

В современных компиляторах, если параметры inline конструктора константы, а результат передаётся другой inline функции, то лучше делать так, как я делал. При моём подходе получаем выигрыш по памяти и "шило на мыло" по скорости выполнения.

Об этом ( о том, что с константами так лучше) написано и в документации библиотеки. Но мы с Вами лучше сами проверим, чтобы знать точно. Итак, сначала текст на память. В моей версии IDE получилось так.

#include <SPI.h>

//Sketch uses 558 bytes (1%) of program storage space
//Global variables use 14 bytes (0%) of dynamic memor
void setup(void) {
	SPI.beginTransaction(SPISettings(F_CPU/2, MSBFIRST, SPI_MODE0));
}

void loop(void) {}

и

#include <SPI.h>

//Sketch uses 612 bytes (1%) of program storage space. Maximum is 30 720 bytes.
//Global variables use 16 bytes (0%) of dynamic memory, leaving 2 032 bytes for
SPISettings set(F_CPU/2, MSBFIRST, SPI_MODE0);
void setup(void) {
	SPI.beginTransaction(set);
}

void loop(void) {}

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

А вот тест на время выполнения (в теории должно быть практически шило на мыло):

#include <SPI.h>

void test1(void) {
	for (int i = 0; i < 30000; i++) {
		SPI.beginTransaction(SPISettings(F_CPU/2, MSBFIRST, SPI_MODE0));
	}
}

void test2(void) {
	SPISettings set(F_CPU/2, MSBFIRST, SPI_MODE0);
	for (int i = 0; i < 30000; i++) {
		SPI.beginTransaction(set);
	}
}

void setup(void) {
	Serial.begin(115200);
	{
		long unsigned start = millis();
		test1();
		long unsigned duration = millis() - start;
		Serial.print("Test1: ");
		Serial.print(duration);
		Serial.println("ms");
	}
	{
		long unsigned start = millis();
		test2();
		long unsigned duration = millis() - start;
		Serial.print("Test2: ");
		Serial.print(duration);
		Serial.println("ms");
	}
}

void loop(void) {}

// РЕЗУЛЬТАТ
//	Test1: 38ms
//	Test2: 39ms

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

---------------------

Если интересно, я могу рассказать почему так (что там за теория такая), но, сами понимаете, это уже узко-специальные вещи - как пишут на Лурке "много матана".

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

Какие нафиг "специальные вещи"!? Оптимизация константных выражений, просто ранее компиляторы умели это не для конструкторов, а с некоторых пор вспомнили что иниченый константами конструктор - тоже в принципе константа и на него константные выражения распостранили, ну и инлайн удачно подвернулся. Но надо учитывать что инлайн - не догма, компилятор может и передумать, например при нескольких вызовах  в разных местах SPI.beginTransaction(SPISettings(F_CPU/2, MSBFIRST, SPI_MODE0)); вместо выиграша будет пролет. 

Но даффайте от темы не уходить, она довольно интересная а  SPI.beginTransaction вобще ниче не дает.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Евгений, вынос из тела цикла того, что не зависит от параметна цикла - с таким компиляторы, действительно, справляются уже давно. Но у Вас другой случай: в цикле вызывается функция update(), причем отнюдь не inline. И в этом случае внутренность функции, насколько мне известно, за пределы цикла, из которого вызывается функция, не выносится.

Соответственно, Ваша модельная задача совершенно не моделирует основную.

Волшебник
Offline
Зарегистрирован: 22.12.2016

andriano пишет:

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

Теперь понятно, у Вас проблема не технического, а психологического плана. Налицо осознание необходимости внедрения диспетчера (супервайзера, планировщика - как угодно) и нежелание это делать. А придётся, у меня не было проэктов такой сложности, диспетчера на ДУЕ я ставил и в более простых скетчах. Есть ресурсы, и есть борьба за них. К ресурсам относятся аппаратные устройства и процессорное время. Хорошо что память пока не точка конфликта. Без старого доброго тоталитаризма, в лице диспетчера, никак.  Демократия погубит проэкт. Планировщик не ставится в прерывание, он заполняет таблицы кому чего посчитать/ отправить, а прерывание тупо эти таблицы процесит.

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

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Волшебник пишет:

... у Вас проблема не технического, а психологического плана.

Спасибо, доктор. )

Если все именно так, то еще есть время на то, чтобы смириться с этой мыслью. Каждая очередная итерация проекта включает:

1. Анализ существующих недостатков и выбор способа их преодоления.

2. Составление принципиальной схемы исправленного устройства.

3. Заказ деталей нав Али.

4. Ожидание доставки.

5.Получение посылки.

6. Изготовление платы.

7. Адекватное изменение софта.

Пока я еа первой стадии текущей итерации, а "перестать метаться" нужно только к 7-й. )

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

andriano пишет:

Евгений, вынос из тела цикла того, что не зависит от параметна цикла - с таким компиляторы, действительно, справляются уже давно. Но у Вас другой случай: в цикле вызывается функция update(), причем отнюдь не inline. И в этом случае внутренность функции, насколько мне известно, за пределы цикла, из которого вызывается функция, не выносится.

Соответственно, Ваша модельная задача совершенно не моделирует основную.

Дело там вовсе не в выносе из тела цикла, а в том, что вместо честного конструктора туда попадает уже готовая(ые) константа(ы) и попадает прямо "куда надо", т.к. все вычисления выполняются на этапе компиляции. Так что там уже н ечего выносить из тела цикла. Абсолютно точно моделирует. 

UPD: я тут подумал, если Вы считаете, что это вынос из цикла, попробуйте убрать циклы из функций, а сами функции вызывать в циклах. Можете даже функции static объявить, чтобы не де Бог не с'inline'ились. И увидите, что ничего существенно не изменится. Вызов конструктора в параметре в данном случае даёт выигрыш (разумеется, параметры конструктора должны оставаться константами).

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

Logik пишет:

Какие нафиг "специальные вещи"!? Оптимизация константных выражений, просто ранее компиляторы умели это не для конструкторов, а с некоторых пор вспомнили что иниченый константами конструктор - тоже в принципе константа и на него константные выражения распостранили, ну и инлайн удачно подвернулся.

Вашу точку зрения о том, что

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

я знаю. Вы не видите там науки и не понимаете её, потому считаете. что её там нет. Ваше право.

Только не зная основ, очень трудно делать правильные выводы. Например, Ваш замечательный пассаж

Logik пишет:
при нескольких вызовах  в разных местах SPI.beginTransaction(SPISettings(F_CPU/2, MSBFIRST, SPI_MODE0)); вместо выиграша будет пролет.

разбивается о простую проверку. Давайте, чтобы не болтать голословно, посмотрим. Даже усложним задачу - поставим в каждом вызове разные константы, чтобы не казалось, что он один раз посчитал и везде подставил. Итак, вызываем 15 раз с разными константами

#include <SPI.h>

//Sketch uses 676 bytes (2%) of program storage space. Maximum is 30 720 bytes.
//Global variables use 14 bytes (0%) of dynamic memory, leaving 2 034 bytes for local variables. Maximum is 2 048 bytes.
void setup(void) {
	SPI.beginTransaction(SPISettings(F_CPU/2, MSBFIRST, SPI_MODE0));
	SPI.beginTransaction(SPISettings(F_CPU/2, MSBFIRST, SPI_MODE1));
	SPI.beginTransaction(SPISettings(F_CPU/2, MSBFIRST, SPI_MODE2));
	SPI.beginTransaction(SPISettings(F_CPU/2, MSBFIRST, SPI_MODE3));
	SPI.beginTransaction(SPISettings(F_CPU/3, MSBFIRST, SPI_MODE0));
	SPI.beginTransaction(SPISettings(F_CPU/3, MSBFIRST, SPI_MODE1));
	SPI.beginTransaction(SPISettings(F_CPU/3, MSBFIRST, SPI_MODE2));
	SPI.beginTransaction(SPISettings(F_CPU/3, MSBFIRST, SPI_MODE3));
	SPI.beginTransaction(SPISettings(F_CPU/4, MSBFIRST, SPI_MODE0));
	SPI.beginTransaction(SPISettings(F_CPU/4, MSBFIRST, SPI_MODE1));
	SPI.beginTransaction(SPISettings(F_CPU/4, MSBFIRST, SPI_MODE2));
	SPI.beginTransaction(SPISettings(F_CPU/4, MSBFIRST, SPI_MODE3));
	SPI.beginTransaction(SPISettings(F_CPU/5, MSBFIRST, SPI_MODE1));
	SPI.beginTransaction(SPISettings(F_CPU/5, MSBFIRST, SPI_MODE2));
	SPI.beginTransaction(SPISettings(F_CPU/5, MSBFIRST, SPI_MODE3));
}

void loop(void) {}

а теперь те же 15 раз с заранее определённой переменной

#include <SPI.h>

//Sketch uses 784 bytes (2%) of program storage space. Maximum is 30&nbsp;720 bytes.
//Global variables use 16 bytes (0%) of dynamic memory, leaving 2&nbsp;032 bytes for local variables. Maximum is 2&nbsp;048 bytes.
SPISettings set(F_CPU/2, MSBFIRST, SPI_MODE0);
void setup(void) {
	SPI.beginTransaction(set);
	SPI.beginTransaction(set);
	SPI.beginTransaction(set);
	SPI.beginTransaction(set);
	SPI.beginTransaction(set);
	SPI.beginTransaction(set);
	SPI.beginTransaction(set);
	SPI.beginTransaction(set);
	SPI.beginTransaction(set);
	SPI.beginTransaction(set);
	SPI.beginTransaction(set);
	SPI.beginTransaction(set);
	SPI.beginTransaction(set);
	SPI.beginTransaction(set);
	SPI.beginTransaction(set);
}

void loop(void) {}

Ну, и где пролёт? Нет никакого пролёта и не будет потому, что там проводится реальная оптимизаци на основе частичных вычислений. То, что в программмировании нет науки и теории, расскажите кому-нибудь другому.

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

"Застав дурака богу молится..." - Ваш дивиз, ЕвгенийП.

А из каких научных соображений Вы остановились на 15 вызовах? Надо было продолжать исследование в сторону больших чисел )))

Проверим, имеет ли премущество оптимизация константных выражений.

 

Да имеет, 54 байта экономим.

Продолжим писать код, а то пустой луп плохо смотрится.

Ой, а что сталось с экономией?

Так что продолжайте ЕвгенийП, наращивать кол-во одинаковых вызовов ))) Пилите, ЕвгенийП, пилите... Или на брайнфаке чего напишите, и обязательно так, чтоб данные исполнялись ;) Пудрить мозги безполезной фигней - Ваше кредо и ваша наука.

Но только НЕ НАДО ЗАФЛУЖИВАТЬ ТЕМУ!!!!

ПС. Наблюдается еще занятный момент в этом примере. Если константы в SPISettings set(F_CPU/2, MSBFIRST, SPI_MODE0); и  SPI.beginTransaction(SPISettings(F_CPU/2, MSBFIRST, SPI_MODE0)); взять не одинаковые, а разные, то памяти расходуется.. меньше на 2 байта 8/ Но то мелочи не достойные внимания.

 

.

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

Logik,

сравните Ваши картинки - левую верхнюю и левую нижнюю.

Вы ведь в нижней сделали не константы в двух разных местах, а к константному вызову добавили неконстантый. Он и съел Вашу экономию. Поставьте два константын и экономия останется.

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

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

А чего это я должен везде в коде только константы использовать? Не дождетесь! Есть фрагмент кода который как бы чего экономит. Ну вот пусть и экономит, а не диктует остальной программе как её писать. 

Если я в коде запишу 2+2, то компилятор его чесно заменит на 0х04 и соответствено немного сэкономит. И результат никак не зависит от моих последующих писаний. Это нормально. А предложение писать код только на константах... Вы вобще себя нормально чувствуете? 

Мало того, даже писать на одних константах не поможет, если inline функция достаточно большая, компилятор очень быстро от инлайности откажится и экономии опять не будет.

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

Да он то экономит! 

Ну, смотрите, я тут битый час объясняю, что собственно экномия берётся за счёт того, что при константах ВСЁ до готовых значений  считается компилятором и не нужно вставлять никакого кода для расчёта в исполняемую программу. А при переменных вставлять код для расчёта нужно. Вот она и экономия отсюда берётся.

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

Понимаете?

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

ЕвгенийП пишет:

ВСЁ до готовых значений  считается компилятором и не нужно вставлять никакого кода для расчёта в исполняемую программу.

Я рад что вы это понимаете, но рассматриваемый случай - не 2+2. Здесь вызов конструктора, который в общем случае может делать много разного (распределять память, создавать списки и  пр.. ) его код, именно последовательность команд достаточно большой и компилер сам будет решать, делать инлайн с константной оптимизацией или нет и обойтись вызовом подпрограммы и соответственно константной оптимизации не будет. Если вызовов много, конструктор большой и иногда при вызовах могут и переменные быть то выбор будет в пользу подпрограммы. Что я и продемонстрировал.

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

Еще раз обращаюсь к Вам, ЕвгенийП! Хватит флудить! Хотите узнать больше про тонкости оптимизации- открывайте для этого тему, спрашивайте, обясню. Но тут тема и без того очень интересная.

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

Logik пишет:

Если я в коде запишу 2+2, то компилятор его чесно заменит на 0х04 и соответствено немного сэкономит. И результат никак не зависит от моих последующих писаний. 

Что значит не зависит? Вся Ваша последующая программа не будет занимать места?

Он и здесь заменил на вычисленные константы (точно как на Вашу 4), а потом Вы вставили другую конструкцию, которая заняла своё место и заняла его независимо от того, что там выше было написано. Вставьте ещё что-нибудь, оно тоже место займёт.

Logik пишет:
А предложение писать код только на константах... Вы вобще себя нормально чувствуете?
Писать вызовы beginTransaction всегда с константами? ОТлично себя чувствую и всегда так пишу. А Вы?

-------

В общем, у меня твёрдое ощущение. что Вы уже всё поняли, а продолжаете что-то возражать ...  ну, Вы понимаете.

Да, кстати, заявление, что наука в программировании - "бесполезная фигня" напоминает позицию свиньи, которой дуб был пофигу, а нужны только жёлуди.

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

Logik пишет:

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

Нет, Вы этого не продемонстрировали. В Вашем примере в в верхнем вызове было все соптимизировано до констант, а в нижнем - нет. Ничего другого Вы не продемонстриировали.

И запомните, компилятор сам ничего не решает. Он пишется на основе определённой теории, которую Вы называете бесполезной фигнёй.

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

Logik пишет:

Еще раз обращаюсь к Вам, ЕвгенийП! Хватит флудить! 

Кто начал?

Logik пишет:

спрашивайте, обясню. 

:)))))))))) Давно так не смеялся. Спасибо, Всё что могли, Вы уже объяснили в этой теме.

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

Смех без причины раньше был признаком эсэсовца, теперь и у Вас истерики?

ЕвгенийП пишет:

Он и здесь заменил на вычисленные константы (точно как на Вашу 4), а потом Вы вставили другую конструкцию, которая заняла своё место и заняла его независимо от того, что там выше было написано. 

Зря вы упираетесь защищая свою глупость, есть дизассемблер ведь.

000000dc <setup>:
  dc:	80 e5       	ldi	r24, 0x50	; 80
  de:	91 e0       	ldi	r25, 0x01	; 1
  e0:	0e 94 54 00 	call	0xa8	; 0xa8 <_ZN8SPIClass16beginTransactionE11SPISettings>
  e4:	08 95       	ret

000000e6 <loop>:
  e6:	80 91 00 01 	lds	r24, 0x0100
  ea:	90 91 01 01 	lds	r25, 0x0101
  ee:	0c 94 54 00 	jmp	0xa8	; 0xa8 <_ZN8SPIClass16beginTransactionE11SPISettings>

 

Как видите инлайна нет. Как и мистической "другую конструкцию". Есть просто вызов подпрограммы call 0xa8 ; 0xa8 <_ZN8SPIClass16beginTransactionE11SPISettings>. И это очевидно, т.к. код даже с издержками на передачу параметров занимает  места меньше чем... её же код плюс остатки от удачной константной оптимизации инлайнового конструктора. И удачненько завершение лупа получилось к тому же.

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

Блин, вот скажите всё-таки, Вы и впрямь не понимаете, что в верхнем случае это ВЕСЬ код,  а в нижнем - есть ещё кусок, который остался во второй строке Вашей программы - вызове конструктора и который Вы здесь не привели? И вот именно на тот код и была экономия по памяти, т.к. в верхнем случае его нет, а в нижнем - есть?

Боюсь, что Вы всё понимаете, просто "один раз ляпнул, а теперь "усрусь, но не признаю, что не прав"". Мне такая форма ведения дискуссии (я это называю подлогом) немного претит. До свидания, не буду больше Вас утомлять. 

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

ЕвгенийП пишет:

Блин, вот скажите всё-таки, Вы и впрямь не понимаете, что в верхнем случае это ВЕСЬ код,  а в нижнем - есть ещё кусок, который остался во второй строке Вашей программы - вызове конструктора и который Вы здесь не привели? 

А нервы у Вас ни к черту ;) Мысль стала сбивчивая, речь путаная.

Что я не привел? Приведены setup и loop полученые при компиляции скетча 

#include <SPI.h>
 SPISettings set(F_CPU/2, MSBFIRST, SPI_MODE0);
void setup(void) {
  SPI.beginTransaction(SPISettings(F_CPU/2, MSBFIRST, SPI_MODE0));
}

void loop(void)
{
  SPI.beginTransaction(set);
}

Того самого, что и во втором моем скрине слева. В котором исчезла экономия места т.к. компилятор похерил константные вычисления. В ассемблере это видно однозначно. Конструктор вызывается подпрограммой, константы передаются как и переменные, через регистры 24 и 25. 

///вызове конструктора и который Вы здесь не привели? 

Что я не привел? В сишном два вызова конструктора, с константой в сетапе и переменной в лупе. Ассм - аналогично. Константной оптимизации и инлайна не обнаружено. Компилятор все похерил и перешел к наиболее общему способу выполнения.

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

Не, ну я всё понял и у же попрощался. Эта музыка будет вечной. Я написал, что с константами также по времени и оптимальнее по памяти. Вы же смешали в кучу и то и другое вместе и что-то доказываете. Доказывайте кому-нибудь другому. 

Ладно, в последний раз, поытаюсь объяснить смысл частичных вычислений на более простом примере. Просто я привык относитья к Вам более серьёзно, чем к Архату (тому бы ничего объяснять не стал) и, всё же надеюсь, что здесь недоразумение, а не упёртость.

Вы всё время говорите про какую-то оптимизацию констант. Поймите, речь идёт не о замене 2+2 на 4. Вовсе не об этом. Это прошлый век. Современная теория компиляции включает в себя некоторые (пока не все) элементы алгебры программ, а именно теории частичных вычислений. Суть её в том, что константы подставляются в код и код вычисляется до тех пор, пока это возможно. Результатом может стать то, что алгоритм из квадратичного превратится в линейный а некоторые функции исчезнут вовсе. Например Если у Вас есть функция выдающая сумму a+b, а Вы обратитесь к ней sum(2, b), она вполне может скомпилироваться в функцию с одним аргументом, которая прибавляет к аргументу двойку. А если Вы к ней обратитесь sum(2,3), она просто может замениться на 5. Заметьте не 2+3 заменилось на 5, а вызов функции заменился на уже готовый её результат.

Это очень продвинутые техники и поначалу они реализовывались только на специально для этого созданных эзотерических языках, на которых это делать было удобно. Сейчас это уже потихоньку переходит в промышленные языки. Например, частично это реализовано в gcc. Посмотрите, как лихо он заменяет рекурсивное (!!!) вычисление факториала на готовый результат. Не берите в голову присваивание порту B - смысла не имеет, зато операция простая - дизасемблер не загаживает. Нас интересует как он факториал скомпилирует, а куда присвоить результат - какая разница. Смотрите:

// Вычисление факториала
inline long fact(const long n) {
	return n < 1 ? 1 : n * fact(n - 1);
}

void setup() {
  PORTB = fact(5);
}

void loop(void){}

А вот во что это дизасемблировалось

setup:
	ldi r24,lo8(120)
	out 0x5,r24
	ret

Как видите, компилятор просто вычислил эту рекурсивную функцию сам и в коде оставил только готовый результат - 120. Вот это - современная оптимизация, а не 2+2 на 5 заменять.

Если Вы считаете, что это результат изобретательности авторов компилятора и науки здесь нет - Ваше право, мне всё равно. Если же Вы скажете, что это опять нафиг никому не нужное изнасилование Вашего мозга, то у нас разные взгляды на красоту в программировании. Просто не читайте моих постов - берегите девственность мозгов.

P.S. Я несколько раз подчёркивал. что функция рекурсивная потому, что в этом особая сложность - нет никакого способа заранее узнать завершится ли она когда-нибудь.

Клапауций 823
Клапауций 823 аватар
Offline
Зарегистрирован: 13.01.2017

Logik пишет:

Еще раз обращаюсь к Вам, ЕвгенийП! Хватит флудить! Хотите узнать больше про тонкости оптимизации- открывайте для этого тему, спрашивайте, обясню. Но тут тема и без того очень интересная.

шо за фигня тут происходит?

флудер в законе здесь я. а, ЕвгенийП - чепуха пидагогическая.

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

Так к Вам, Клапауций уже все привыкли, при всех недостатках у Вас огромный плюс - краткость ;)

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

ЕвгенийП пишет:

Дело там вовсе не в выносе из тела цикла...

Проверил - действительно, я был не прав: компилятор подвергает интерпретации не только константные выражения, но и константные алгоритмы с ветвлением.