ARDUINO Лаг в "while (next_time > millis()) {}" и борьба с ним

Ivani

✩✩✩✩✩✩✩
7 Ноя 2022
9
0
Конструкция
next_time = millis() + 2;
while (next_time > millis()) {}
использовалась вместо запуска по таймеру, в какой то момент стал заметен рассинхрон, создал скетч специально для проверки, запускался на Ардуино Нано с ATmega328P.
скетч проверки:
void setup() {
  Serial.begin(115200);
  Serial.println("start");

}

void loop() {

  uint32_t next_time = 0;
  uint32_t time_1 = 0;
  uint32_t time_2 = 0;

  next_time = millis() + 10;
  while (next_time > millis()) {}
  time_1 = micros();
  time_2 = millis();
  Serial.print(" d_time = ");
  Serial.print(time_2 - next_time);
  Serial.print(" m_time = ");
  Serial.println(time_1 - next_time * 1000);

}
результат, m_time - задержка от установленного времени до времени выхода из этой паузы в микросекундах:
start
d_time = 0 m_time = 272
d_time = 0 m_time = 512
d_time = 0 m_time = 752
d_time = 0 m_time = 992
d_time = 0 m_time = 208
d_time = 0 m_time = 448
d_time = 0 m_time = 684
d_time = 0 m_time = 928
d_time = 0 m_time = 144
d_time = 0 m_time = 384
d_time = 0 m_time = 620
d_time = 0 m_time = 864
d_time = 0 m_time = 80
d_time = 0 m_time = 320
d_time = 0 m_time = 560
d_time = 0 m_time = 800
d_time = 0 m_time = 16
d_time = 0 m_time = 256
d_time = 0 m_time = 496
d_time = 0 m_time = 736
d_time = 0 m_time = 976
d_time = 0 m_time = 192
d_time = 0 m_time = 432
d_time = 0 m_time = 672
d_time = 0 m_time = 912
d_time = 0 m_time = 128
d_time = 0 m_time = 368
d_time = 0 m_time = 608
d_time = 0 m_time = 848
d_time = 0 m_time = 64
d_time = 0 m_time = 304
d_time = 0 m_time = 544
d_time = 0 m_time = 784
d_time = 1 m_time = 1024
d_time = 0 m_time = 264
d_time = 0 m_time = 504
Примерно тоже самое но с micros() работает с лагом максимум 12 мкс.

с micros() с лаг максимум 12 мкс:
void setup() {
  Serial.begin(115200);
  Serial.println("start");

}

void loop() {

  uint32_t next_time = 0;
  uint32_t time_1 = 0;

  next_time = micros() + 200;
  while (next_time > micros()) {}
  time_1 = micros();
  Serial.print(" m_time = ");
  Serial.println(time_1 - next_time);

}

start
m_time = 4
m_time = 4
m_time = 0
m_time = 0
m_time = 4
m_time = 4
m_time = 4
m_time = 4
m_time = 4
m_time = 4
m_time = 4
m_time = 4
m_time = 4
m_time = 4
m_time = 4
m_time = 4
m_time = 4
m_time = 4
m_time = 4
m_time = 4
m_time = 4
m_time = 4
m_time = 0
m_time = 4
m_time = 4
m_time = 4
Подскажите пожалуйста как победить лаг в "while (next_time > millis()) {}" кроме как использовать таймер или перейти на micros()?
 

poty

★★★★★★✩
19 Фев 2020
3,213
934
1. Такая конструкция задания временных интервалов не работает при переполнении счётчика millis/micros.
2. Использование "программных" таймеров всегда зависит от другого кода, выполняющегося внутри loop. Решение многократно озвучивал @Старик Похабыч : вызывать сравнение для определения интервала как можно чаще. Приводился также код, оценивающий время выполнения цикла.
В Вашем случае нужно вынести все Serial "за скобки" цикла и уменьшить количество вычислений с unsigned long внутри него.
 

Ivani

✩✩✩✩✩✩✩
7 Ноя 2022
9
0
Цикл ПУСТОЙ "while (next_time > millis()) {}" это чисто ожидание до наступления определенного времени и непонятно как пустой цикл с 1 условием так сильно промахивается. Притом такой-же цикл с микрос() работает адекватно.
 

Сотнег

★★★★★★★
15 Янв 2020
4,305
1,477

@Ivani,
while (next_time > millis()) когда-нибудь будет довольно долго промахиваться, если next_time = максимально возможное число.
 
Изменено:

Ivani

✩✩✩✩✩✩✩
7 Ноя 2022
9
0
Перед ним "next_time = millis() + 10;" в оригинале было "next_time = millis() + 2;" я готов "пожертвовать" 2 мс.


"
 

Сотнег

★★★★★★★
15 Янв 2020
4,305
1,477
Подскажите пожалуйста как победить лаг в "while (next_time > millis()) {}" кроме как использовать таймер или перейти на micros()?
Если вы делаете задержку на millis(), тогда лаг в 1 мс вас должен устраивать.
d_time = 0
d_time = 1 - вполне нормально.
Странно, что задержка проверяет состояние millis(), а лаг вы почему-то проверяете в микросекундах.

next_time = millis() + 10;
В момент вычисления этой строчки до увеличения millis на единицу может оставаться меньше микросекунды.
 

poty

★★★★★★✩
19 Фев 2020
3,213
934
Действительно, смотрел с телефона, не увидел закрывающей скобки в цикле.
я готов "пожертвовать" 2 мс
Так у Вас задержка m_time измеряется в микросекундах (я снова невнимательно оценил на ходу код) и не превышает 1мс. Больше 1мс точности Вы в любом случае не получите. Одну из причин озвучил @Сотнег , но есть ещё время выполнения самого while и последующие вычисления. Они, конечно, выполняются микросекунды, но всё же.
 

bort707

★★★★★★✩
21 Сен 2020
3,039
908
Конструкция
next_time = millis() + 10;
while (next_time > millis()) {}
Эта ваша "конструкция" - полный аналог оператора delay(10).
Если вам нужна блокирующая задержка - используйте делеи и не "выпендирвайтесь" с миллис
Нафига столько букв, если можно сделать проще.
 

Ivani

✩✩✩✩✩✩✩
7 Ноя 2022
9
0
Вставил,
" next_time = millis() + 10;
while (next_time > millis()) {
asm volatile ("nop\n\t");
}"
все так-же лагает.

"next_time = millis() + 10;
do {
asm volatile ("nop\n\t");
} while (next_time > millis());"
все так-же лагает.
 

Bruzzer

★★★✩✩✩✩
23 Май 2020
451
130
@Ivani,
Пустые циклы активно используются. Например очень часто
while (!Serial.available());
Так, что дело не в этом.
вам правильно написали, что мерить миллисекундные задержки при помощи millis не стоит. Правильнее использовать micros, что собственно и делает delay. Почему вы не хотите использовать delay ?
 

Сотнег

★★★★★★★
15 Янв 2020
4,305
1,477
@Ivani, что, блин, лагает-то?
Лог полностью соответствует коду.
Во всех строчках либо 0 либо 1.
Чего вам не нравится?
 

bort707

★★★★★★✩
21 Сен 2020
3,039
908
@Ivani, всегда лучше использовать то, в чем разбираетесь - меньше ошибок насажаете.
Цикл while с миллис - это 100% подтверждение того, что вы не понимаете как этим миллис пользоваться.
 

Ivani

✩✩✩✩✩✩✩
7 Ноя 2022
9
0
@Ivani,
Почему вы не хотите использовать delay ?
Как с помощью delay отмерить 20 интервалов от одной точки?
Далее
"next_time += 1;
while (next_time > millis()) { }"
позволяет отмерять от начальной точки.

@IvaniЦикл while с миллис - это 100% подтверждение того, что вы не понимаете как этим миллис пользоваться.
Как пользоваться понимаю, не знал что в ядре Ардуино он тухлый. Теперь разобрался как он в ядре Ардуино считается - по переполнению таймера 0 - раз в 1024 мс и сколько его не опрашивай между пересчетами значение не меняется, рандомно может отставать от micros() более чем на 1 мс.
Циклы(задержка до определенного времени) "while (next_time > millis()) { }" и "while (next_time > micros()) { }" вполне рабочие но в среде Ардуино возможности ограничены реализацией millis().
 

poty

★★★★★★✩
19 Фев 2020
3,213
934
Циклы(задержка до определенного времени) "while (next_time > millis()) { }" и "while (next_time > micros()) { }" вполне рабочие
нет, если значение, возвращаемое функциями при инициализации next_time отличается от максимального для unsigned long на значение задержки, то вычисление с плюсом приводит к переполнению и условие "больше" выполняется сразу.
Счётчики millis и micros инкрементируются постоянно, независимо от вызова функции и это не обязано совпадать с тем, как Вы ожидали, как это должно работать. Если Вы подумаете, то другого варианта для случая вызова одной функции и быть не может.
 

Ivani

✩✩✩✩✩✩✩
7 Ноя 2022
9
0
Для этого нужно как минимум загнать аптайм за 49 суток, я стараюсь ребутить мозги на 7 - 24 сутки от старта. Причин для этого несколько, основные это не уверенность что в сторонних либах нет времени мс в int32 и возможное накопление ошибок/утечка памяти.
 

poty

★★★★★★✩
19 Фев 2020
3,213
934
@Ivani, для millis - да, для micros это в 1000 раз быстрее. Не понимаю, почему бы не сделать правильно без оговорок и перезагрузки.
 

poty

★★★★★★✩
19 Фев 2020
3,213
934
@Ivani, никаких причин, кроме опасений, я не увидел. Большинство библиотек в Ардуино - с открытым кодом, не составит никакого труда посмотреть их код на предмет проблем. Не всех, а тех, которые нужны. Есть отличные прецеденты на этом сайте (@Sergo_ST , например), который вполне обходится без библиотек.
Ошибки устраняются тестированием и отладкой.
Вариант получения точного времени здесь, на сайте, был и с помощью RTC, и по GPS, и через NTP... Это если нужно точное время, так как МП в любом случае его не предоставит.
Но это всё лирика. Правильно - это отнимать от millis()/micros() начальный момент и сравнивать с нужной длительностью. Посмотрите, например, библиотеку Гайвера по программным таймерам (не призываю её использовать, с моей точки зрения проще макроподстановками решить), но вариант реализации увидите.
А для точного интервала нужно использовать прерывания разного вида, ещё и код можно будет параллельно выполнять, а не блокировать (это к вопросу о 20 интервалах от начальной точки: зачем они нужны, если между этими интервалами ничего нельзя делать под страхом, что пропустишь очередное сравнение и потеряешь точность).
 
  • Лойс +1
Реакции: bort707 и Sergo_ST

Ivani

✩✩✩✩✩✩✩
7 Ноя 2022
9
0
next_time = micros() + 200;
пре код1
while (next_time > micros()) {}
код1
next_time += 200;
пре код2
while (next_time > micros()) {}
код2
и так далее, код1, код2 и последующие так-же размещенные участки кода будут выполняться через 200 микросекунд с точностью -0 +12 мкс от начальной точки, задержки между кодN и кодN+1 можно сделать разные.
 

Bruzzer

★★★✩✩✩✩
23 Май 2020
451
130
Я думал, что вы когда говорите про измерение интервалов от одной точки имеете в виду что то типа:
C++:
Начало цикла
while (next_time  > millis()) { }
    // задача для next_time
while (next_time + 3 > millis()) { }
    // задача для next_time + 3
while (next_time + 12 > millis()) { }
    // задача для next_time + 12
while (next_time + 24 > millis()) { }
    // задача для next_time + 24
....
next_time += 200;
Переход на Начало цикла
тогда действительно в отличии от delay погрешность не накапливалась бы. (Как уже писали выше сравнение millis тут оставлено"неправильное")
В вашем варианте погрешность тоже не накапливается. Но "одна точка" уже не как очевидна.
 
Изменено:

Ivani

✩✩✩✩✩✩✩
7 Ноя 2022
9
0
Большинство библиотек в Ардуино - с открытым кодом, не составит никакого труда посмотреть их код на предмет проблем...Ошибки устраняются тестированием и отладкой.
Посмотрите видео "Электронный замок с RFID на Arduino" много там библиотек исправлено? Нет как обычно костыль кривой вставлен! Я написал себе обрезанную либу под 3231 с корректором - так замучился что все желание перелопачивать написанное другими пропало.


Я думал, что вы когда говорите про измерение интервалов от одной точки имеете в виду что то типа:
Это точно тоже самое - компилятор повторяющееся сложение вынет из цикла, точность +-1024 мкс от стартового.
Я не буду экспериментировать с аптаймом и буду ребутить сторожевиком раз в неделю, в этом случае вариант с millis() для точности +-1024 мкс оправдан и беспроблемен, варианту с micros() добавлю отсрочку/задержку на переполнение.
 

poty

★★★★★★✩
19 Фев 2020
3,213
934
@Ivani, в приведённом Вами коде "пре код1", "код1",... должны успевать выполняться за отведённое время (по коду - 200мкс), в том числе с учётом всего того, что выполняется по прерываниям. И если Вы выходите за назначенный интервал - Вы теряете точность. Я об этом говорил.
Посмотрите видео "Электронный замок с RFID на Arduino" много там библиотек исправлено? Нет как обычно костыль кривой вставлен! Я написал себе обрезанную либу под 3231 с корректором - так замучился что все желание перелопачивать написанное другими пропало.
Давайте начистоту: спасение утопающих - дело самих утопающих. Библиотеки пишут люди, в том числе и с учётом своих пониманий того, как это должно выглядеть. Вот Вам с самого начала ветки несколько человек говорили, что интервалы высчитываются неверно, но Вы предпочли в своём продукте не прислушаться к мнению других, а
буду ребутить сторожевиком раз в неделю
добавлю отсрочку/задержку на переполнение
Так чем Ваш код будет лучше кода других? Поэтому даже смотреть не буду.
Что касается WS3231 (RTC?), то библиотек - великое множество! Через Wire сделать опрос можно буквально несколькими строками. Напрямую - welcome в альтернативную прошивку для часов на неонках того же @Sergo_ST (это не реклама, а действительно большой и достойный труд). И это - не переписывание за другими, а правильный подход, когда нужно экономить ресурсы для получения своего достойного продукта.
Впрочем, аналогичная ситуация практически со всеми "библиотеками".
 

bort707

★★★★★★✩
21 Сен 2020
3,039
908
@poty, грамотный человек не станет считать интервалы сложением "вперед"

while (next_time + 3 > millis()) { }

независимо от того, будет ли его код работать день или год. А если он еще и отказывается это исправлять, хотя речь идет о паре символов - то смысла дискутировать с ним не вижу.
У подобных авторов всегда вокруг все виноваты - ошибки в чужих библиотеках, в ядре Ардуино, таймеры лагают и тд и тп - но только не в их коде.
 
  • Лойс +1
Реакции: Старик Похабыч