Время задержки); против if (millis () - предыдущий> раз); и дрейф

8

Проходя старый проект, у меня был код на двух Arduino Due, который выглядел так

void loop()
{
  foo();
  delay(time);
}

принимая к сердцу большинства из литературы об использовании delay();я переделал это как

void loop()
{
  static unsigned long PrevTime;
  if(millis()-PrevTime>time)
  {
    foo();
    PrevTime=millis();
  }
}

Тем не менее, это, кажется, создало ситуацию, когда два устройства дрейфуют в течение периода времени, когда они не делали ранее

Мой вопрос двоякий:

  1. Почему бы if(millis()-PrevTime>time)вызвать больше дрейфа, чем delay(time)?
  2. Есть ли способ предотвратить этот дрейф, не возвращаясь к delay(time)?
ATE-Энж
источник
1
Каков порядок величины «периода времени», по которому вы замечаете дрейф? Находятся ли два устройства в одном и том же месте, и поэтому имеют одинаковую температуру? Они работают на кварцевом генераторе или керамическом резонаторе?
Хосе может
Лично я предпочитаю решение Majenko, и всегда использую его (я ставлю инкремент перед другими инструкциями, но это всего лишь предпочтение). Обратите внимание, однако, что это время ТОЧНО 100 мс, в то время как другой код ( foo; delay;) имеет период более 100 мс (это 100 мс + время foo). Таким образом, вы будете испытывать дрейф (но это - delayреализованный SW, который дрейфует). В любом случае, имейте в виду, что даже совершенно равные реализации «дрейфуют», потому что часы не равны; если вам нужна полная синхронизация, используйте сигнал для синхронизации двух программ.
frarugi87
Два устройства находятся рядом друг с другом, после запуска с пятницы в 17:00 до понедельника в 9:00 произошел дрейф в 4 минуты. Я решаю, что буду использовать цифровой вывод для синхронизации входов в соответствии с вашим предложением
ATE-ENGE
«Два устройства находятся рядом друг с другом, ...» это не означает, что механизм синхронизации не точен, вы говорите о частоте ошибок ~ 800 ppm, высокой для двух кварцевых генераторов, но приемлемой даже для одного керамического резонатора. Вы должны сравнить его с достаточно точным стандартом синхронизации, чтобы быть уверенным: кристаллы обычно находятся в пределах 20 частей на миллион, а tcxo могут делать менее 1 части на миллион. это был бы мой способ сделать это.
Дэнниф

Ответы:

10

Есть одна важная вещь, которую вы должны помнить, работая со временем на Arudino любой формы:

  • Каждая операция требует времени.

Ваша функция foo () займет некоторое время. Что это за время, мы не можем сказать.

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

Например, возьмите следующее:

if (millis() - last > interval) {
    doSomething();
    last = millis();
}

Переменная lastбудет временем, когда подпрограмма сработала * плюс время, doSomethingзатраченное на выполнение. Так, скажем interval, 100, и doSomethingдля запуска требуется 10 мс, вы получите срабатывание при 101 мс, 212 мс, 323 мс и т. Д. Не те 100 мс, которые вы ожидали.

Поэтому вы можете всегда использовать одно и то же время независимо от запоминания определенного момента (как предлагает Джурадж):

uint32_t time = millis();

if (time - last > interval) {
    doSomething();
    last = time;
}

Теперь время, которое doSomething()уходит, ни на что не повлияет. Таким образом, вы получите срабатывание при 101 мс, 202 мс, 303 мс и т. Д. Все еще не совсем те 100 мс, которые вы хотели - потому что вы ищете более 100 мс пройденных - и это означает 101 мс или более. Вместо этого вы должны использовать >=:

uint32_t time = millis();

if (time - last >= interval) {
    doSomething();
    last = time;
}

Теперь, предполагая, что в вашем цикле больше ничего не происходит, вы получаете срабатывание на 100 мс, 200 мс, 300 мс и т. Д. Но обратите внимание на этот бит: «пока в вашем цикле больше ничего не происходит» ...

Что произойдет, если операция, которая занимает 5 мс, происходит при 99 мс ...? Ваш следующий запуск будет отложен до 104 мс. Это дрейф. Но с этим легко бороться. Вместо того чтобы сказать «записанное время сейчас», вы говорите «записанное время на 100 мс позже, чем было». Это означает, что независимо от того, какие задержки вы получаете в своем коде, ваш запуск всегда будет происходить с интервалами в 100 мс или дрейфом в пределах 100 мсек.

if (millis() - last >= interval) {
    doSomething();
    last += interval;
}

Теперь вы получите срабатывание при 100 мс, 200 мс, 300 мс и т. Д. Или, если есть задержки в других битах кода, вы можете получить 100 мс, 204 мс, 300 мс, 408 мс, 503 мс, 600 мс и т. Д. Он всегда пытается запустить его как можно ближе к интервал, насколько это возможно, независимо от задержек. И если у вас есть задержки, превышающие интервал, он автоматически запустит вашу обычную программу достаточно времени, чтобы догнать текущее время.

До того, как вы дрейфовали . Теперь у вас есть дрожание .

Маженко
источник
1

Потому что вы сбрасываете таймер после операции.

static unsigned long PrevTime=millis();

unsigned long t = millis();

if (t - PrevTime > time) {
    foo();
    PrevTime = t;
}
Юрай
источник
нет. обратите внимание, что PrevTime является статическим.
Дэнниф
4
@dannyf, да, а?
Юрай
если бы вы знали, что означает «статический», вы бы знали, почему ваш ответ неверен.
Дэнниф
Я знаю, что делает статический с локальной переменной .. Я не понимаю, почему вы думаете, что мой ответ как-то связан со статическим. Я только что переместил текущее чтение в миллисекундах до вызова foo ().
Юрай
-1

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

Например:

long next_trigger_time = 0L;
long interval = DESIRED_INTERVAL;

void loop() {
   do_something(); // check a sensor or value set by an interrupt
   long m = millis();
   if (m >= next_trigger_time) {
       next_trigger_time = m + interval;
       foo();
   }
}

Это будет запускать foo () в указанный интервал, позволяя циклу продолжать выполнение между ними. Я поместил вычисление next_trigger_time перед вызовом foo (), чтобы помочь минимизировать дрейф, но это неизбежно. Если дрейф является серьезной проблемой, используйте таймер прерывания или какую-либо синхронизацию часов / таймера. Также помните, что millis () будет работать через некоторое время, и я не учел это, чтобы сохранить пример кода простым.

ThatAintWorking
источник
Ненавижу упоминать об этом: проблема опрокидывания через 52 дня.
Я уже упоминал проблему с опрокидыванием в конце моего ответа.
ThatAintWorking
Ну, решите это.
Моя стандартная плата за консультацию $ 100 / час, если вы хотите, чтобы я написал код для вас. Я думаю, что то, что я написал, достаточно актуально.
ThatAintWorking
1
Вы знаете, что Majenko опубликовал более полный и лучший ответ, чем ваш? Вы знаете, что ваш код не компилируется? Это long m - millis()не делает то, что вы собираетесь делать? Это на дом.
-4

ваш код правильный.

проблема, с которой вы сталкиваетесь, связана с функцией millis (): она будет недооценена (максимальный недосчет составляет всего лишь 1 мс на вызов).

Решение заключается в более тонких тиках, таких как micros (), но это также будет немного занижено.

dannyf
источник
2
Пожалуйста, предоставьте некоторые доказательства или ссылку на « это будет недооценено ».
Эдгар Бонет