Что общего у рояля и millis()...

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
Я в Ардуине дуб дубом. Но иногда смотрю скетчи и вот такую вещь заметил - подавляющее большинство даже простыми функциями Ардуины пользуется неправильно. Покажу на примере, взятом прямо с этого форума. Вот есть тема - Помигаем светодиодом. Там какие то простенькие примеры. Вот прямо первый и возьмем оттуда:
C++:
#define LED_1 9
#define LED_2 10
#define TMD 5 // задержка между шагами
void setup() {
  pinMode(LED_1, OUTPUT);
  pinMode(LED_2, OUTPUT);

}
uint8_t step = 0;
uint32_t tmr = 0;
void loop() {
  if (millis() - tmr > TMD)
  {
    tmr = millis();
    analogWrite(LED_1, step);
    analogWrite(LED_2, step + 128);
    step++;
  }
}
Все всем нравится? Мне - нет. Представим ситуацию - Вы хотите купить рояль, но не уверены, что он влезет в вашу комнату. Вы заказали доставку рояля, приехала машина с роялями, вы взяли оттуда рояль и затащили его на 9 этаж. Померили - не входит. И выкинули его в окно. Или - входит и вы... выкинули его в окно и пошли вниз тащить к себе другой, точно такой же рояль. Звучит как бред? Но именно это происходит в примере!
Вызвали millis(), сравнили и если сравнение устроило - вызвали millis() снова. Потом кто то удивляется что Ардуина тормозит?
Там в теме ниже есть пример с ЧЕТЫРЬМЯ вызовами. Хотя в любом варианте достаточно одного. Да, это небольшая потеря времени и немного лишней флеши займет. Но немного там, немного тут - так и набирается.
Да, я ж не сказал как не тащить рояль дважды и четырежды. А это ну очень просто - добавьте локальную переменную и считывайте millis туда. Один раз. А все сравнения и присваивания делайте уже с этой локальной переменной.
 

poty

★★★★★★✩
19 Фев 2020
3,005
899

@Forgetweb, в "общем подходе" я бы с Вами согласился. И даже поддержал бы.
Но в данном случае, возможно, Вы не правы. Можно ведь сравнить длину кода в приведённом примере и примере с временной переменной. Не уверен, что код с временной переменной будет короче. Кроме того, временная переменная попадает в стек, который размещается в МП в той же памяти, что и данные, то есть расход более дефицитного ресурса будет больше.
С точки зрения скорости? Зависит от обстоятельств. "Таймер" по mills() работает как правило так, что многократно проверяется и один раз выполняется. В этом смысле каждый раз при многократной проверке делать лишнюю операцию выделения временной переменной и присвоения ей значения (32 бит между прочим) - не самый правильный вариант, чтобы потом один раз её переиспользовать.
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
@poty,
Я вот уверен что будет короче. Потому что она в регистрах создается. А вовсе не в стеке. Причем она и так и так создастся! Вот в чем дело. 4 регистра - в них вернется значение счетчика. И его сравнивают с чем то еще. Потом эти 4 регистра заменяются новым значением возвращаемым функцией. Компилятор такое не оптимизирует, да и миллис возвращает переменную объявленную как volatile.
Насчет 32бит - а никто не заставляет ее делать 32 бита. Берите 8 бит. Если выдержка времени 5мс, то зачем все 4 байта? Это сократит код еще больше.
 

Геннадий П

★★★★★★✩
14 Апр 2021
1,847
595
44
@Forgetweb, Вы забываете, что по сути это учебная среда программирования ардуино, а не программирование голого контроллера. "Таскать рояли" здесь обычное дело. Если уж заниматься оптимизацией, то всякие analogWrite имеют кучу бесполезной проверки при вызове чтобы случайно не выстрелить себе в ногу. Вот по сравнению с небольшим роялем это целый орган.
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
@Геннадий П,
С этим поспорить трудно. Типа раз учебная, то забиваем на оптимальность? Я могу таких примеров странных решений много привести. Причем из типа учебников. Которые удивляют.
Вот например:
C++:
const int POT0=0;
const int POT1=1;

int val0;
int val1;

void setup(){
  Serial.begin(9600);
}

void loop(){
  val0 = map(analogRead(POT0), 0, 1023, 0, 255);
  val1 = map(analogRead(POT1), 0, 1023, 0, 255);

  Serial.print(val0);
  Serial.print(".");
  Serial.println(val1);
  delay(50);
}
Отсюда
Все нравится?
 

poty

★★★★★★✩
19 Фев 2020
3,005
899

@Forgetweb, так что гадать: берём два кода и сравниваем.
По поводу переменной - достаточно посмотреть код на ассемблере. Одно дело - предполагать, другое - иметь в реальности.
 

Эдуард Анисимов

★★★★★★✩
23 Сен 2019
2,295
947
58
Марий-Эл
Если выдержка времени 5мс, то зачем все 4 байта? Это сократит код еще больше
Если такой умный, почему такой бедный?
millis() универсальная функция.
Я знаю людей, которые под каждую задержку отдельную функцию пишут.
Не нравится, пишите на ASM, там вообще всё круто. А можно на STM32 перейти. Там millis() просто чтение регистра счётчика.
Типа раз учебная, то забиваем на оптимальность?
Вы удивитесь. Всем ардуинщикам на...ть на оптимальность, лишь бы светодиод моргал.
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
@poty, не, не спровоцируете. Я уже однажды повелся на Ваши возражения и смотрел листинг. Напомню - о указателе в обработчике прерывания. Я оказался прав. И сейчас, я уверен, тоже буду прав.
 

Геннадий П

★★★★★★✩
14 Апр 2021
1,847
595
44
Типа раз учебная, то забиваем на оптимальность?
Именно так. Учиться нужно программировать, оптимизация - это уже углубленный курс.
Слышали фразу "преждевременная оптимизация - корень всех зол"? Вот с ней я полностью согласен. Порой пишешь программу, и по пути ее начинаешь оптимизировать (внутренний перфекционист душит), и вот из-за этого уже сосредотачиваешься не на написании самой программы, а на ее оптимизации. И это есть зло.
 
  • Лойс +1
Реакции: Bruzzer и PiratFox

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
@Эдуард Анисимов,
Причем тут универсальность? Это инструмент. И надо им пользоваться оптимально. Зачем считывать одно и то же по несколько раз? И платформа тут не важна. В кортексе просто потери будут меньше, но все равно они будут.
 

Эдуард Анисимов

★★★★★★✩
23 Сен 2019
2,295
947
58
Марий-Эл
@Forgetweb,
C++:
const int POT0 = 0;
const int POT1 = 1;

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  Serial.print(map(analogRead(POT0), 0, 1023, 0, 255));
  Serial.print(".");
  Serial.println(map(analogRead(POT1), 0, 1023, 0, 255));
  delay(50);
}
Так больше устроит?
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
@Геннадий П,
Мой отец говорил мне в детстве - старайся делать хорошо. Плохо может само получиться. Я так и делаю. А миллис... Просто ардуине много лет. А кривое использование ее возможностей все эти годы существует. Почему то.

@Эдуард Анисимов, нет. Вы похоже даже не поняли что мне там не понравилось )
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
@Эдуард Анисимов,
map. Нахрена он там?
Вместо
Serial.print(map(analogRead(POT0), 0, 1023, 0, 255));
пишем
Serial.printanalogRead(POT0)>>2);
Или
Serial.printanalogRead(POT0)/4);
Так то и фик бы с ним, но это типа учебник. А в учебнике должно быть нормально.
А время то не изменится. uint8_t time = millis(); Для перфекционистов - uint8_t time = (uint8_t)millis();
 

Эдуард Анисимов

★★★★★★✩
23 Сен 2019
2,295
947
58
Марий-Эл
Serial.printanalogRead(POT0)>>2);
А если надо map(analogRead(POT0), 0, 1023, 0, 500)
Куда сдвигать будем?

Serial.printanalogRead(POT0)>>2); Это не откомпилируется

Serial.printanalogRead(POT0)/4); Это тоже не откомпилируется. И уже есть ошибка. Здесь не будет небольшого кода. Если компилятор дурной. Он её развернёт в алгоритм деления скорее всего, а он довольно немало места занимает.
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
@Эдуард Анисимов,
нет, просто не напишем такое и все. Зачем код шмонать? Делать надо сразу нормально.

@Bruzzer,
Там ровно те же логические косяки, что и в приведенном коде. 1 в 1.
 

Эдуард Анисимов

★★★★★★✩
23 Сен 2019
2,295
947
58
Марий-Эл
@Forgetweb, В 99.9% случаях это избыточно.
Такие вещи нужны крайне редко. И этого достаточно.
Предлагаемый Вами метод никогда практически не понадобится.
Кода работаете с датчиками, дающими аналоговый сигнал, Вы столкнётесь с тем, что они имеют разброс до 10%. Если нужна высокая точность, Вам придётся подгонять переменные в функции map(). Если такой задачи нет, можно загрублять. По Вашему методу.
Возьмём ESP32. У неё и так АЦП говно. А теперь понизим напряжение с помощью AMS1117-33. У неё разброс
1690919616719.png
А нужно померять напряжение с точностью два знака после запятой. И таких устройств 10 штук.
Придётся для каждого устройства измерять напряжение и заносить константу в функцию map(). Ну и где там сдвигом можно обойтись?
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
@Эдуард Анисимов,
Там была всего лишь задача из 10 бит сделать 8. Я такого с мапом видел много. Потом это шло например в шим. То есть это вообще не датчик, а тупой резистор.
А калибровать датчики с кривым АЦП нужно по таблицам. Мап линейный и кривой АЦП исправить не сможет никак.
Я с кривыми датчиками работаю лет 20 ) И ни разу никаких мапов не использовал. Да в принципе и не собираюсь. И смысл был не в том, что мап функция отстой, а в том что ее лепят туда, где она не нужна.
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
Если бы с рождения. Как работать пошел разработчиком РЭА так и пришлось.
Мне просто интересно почему за 10 лет существования Ардуины так и используют ее вкривь и вкось.
Лобзиком кому то придет в голову пилить дерево? Едва ли. А тут такое сплошь и рядом. И это не значит что лобзик плохой.
 

Сотнег

★★★★★★★
15 Янв 2020
4,139
1,445
Вызвали millis(), сравнили и если сравнение устроило - вызвали millis() снова.
Обычно "вызов millis()" людьми воспринимается, как использование значения глобальной переменной.
При таком восприятии ничего удивительного, что мало желающих пересохранять её в локальную переменную.