tempo de atraso); vs if (millis () - horário anterior>); e deriva


8

Passando por um projeto antigo, eu tinha código em dois Arduino Due que eram parecidos com este

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

levando a sério a maioria da literatura sobre o uso delay();eu recodifiquei isso como

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

No entanto, isso parece ter criado uma situação em que os dois dispositivos flutuam ao longo de um período em que não o faziam anteriormente

Minha pergunta é dupla:

  1. Por que if(millis()-PrevTime>time)causaria mais desvio do que delay(time)?
  2. Existe uma maneira de evitar esse desvio sem voltar para delay(time)?

11
Qual é a ordem de magnitude do "período de tempo" durante o qual você percebe a deriva? Os dois dispositivos estão no mesmo local e, portanto, na mesma temperatura? Eles são executados em um oscilador de cristal ou em um ressonador de cerâmica?
Jose can uc

Pessoalmente, prefiro a solução de Majenko e sempre a uso (coloquei o incremento antes das outras instruções, mas isso é apenas uma preferência). Observe, no entanto, que esse tempo é PRECISAMENTE 100 ms, enquanto o outro código ( foo; delay;) tem um período maior que 100 ms (é 100 ms + tempo de foo). Então você experimentará a deriva (mas é o delaySW implementado que está à deriva). De qualquer forma, lembre-se de que mesmo implementações perfeitamente iguais "flutuam", porque os relógios não são iguais; se você precisar de sincronização completa, use um sinal para, bem, sincronizar os dois programas.
precisa saber é o seguinte

Os dois dispositivos estão próximos um do outro, depois de executar de sexta-feira às 17:00 a segunda-feira às 9:00, houve um desvio de 4 minutos. Estou decidindo que eu vou usar um pino digital para sincronizar as entradas como por sua sugestão
ATE-ENGE

"Os dois dispositivos estão próximos um do outro, ..." isso não significa que o mecanismo de temporização não seja preciso. Você está falando de uma taxa de erro de ~ 800ppm, alta para dois osciladores de cristal, mas razoável para até um ressonador de cerâmica. você deve compará-lo a um padrão de tempo razoavelmente preciso para ter certeza: os cristais geralmente estão dentro de 20 ppm e os tcxo podem fazer abaixo de 1 ppm. essa seria a minha maneira de fazer isso.
Dannyf 4/12

Respostas:


10

Há uma coisa importante que você precisa lembrar ao trabalhar com o tempo em um Arudino de qualquer forma:

  • Toda operação leva tempo.

Sua função foo () levará um tempo. Que horas são, não podemos dizer.

A maneira mais confiável de lidar com o tempo é confiar apenas no tempo para acionar, e não trabalhar quando o próximo acionamento deve ser.

Por exemplo, tome o seguinte:

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

A variável lastserá o tempo que a rotina disparou * mais o tempo doSomethingnecessário para executar. Digamos que intervalé 100 e doSomethingleva 10ms para funcionar, você receberá disparos a 101ms, 212ms, 323ms, etc. Não os 100ms que você estava esperando.

Portanto, uma coisa que você pode fazer é sempre usar o mesmo tempo, independentemente, lembrando-o em um momento específico (como sugere Juraj):

uint32_t time = millis();

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

Agora, o tempo doSomething()gasto não terá efeito em nada. Então você terá disparos a 101ms, 202ms, 303ms, etc. Ainda não exatamente os 100ms que você queria - porque você está procurando mais de 100ms - e isso significa 101ms ou mais. Em vez disso, você deve usar >=:

uint32_t time = millis();

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

Agora, supondo que nada mais aconteça no seu loop, você recebe disparos a 100ms, 200ms, 300ms, etc. Mas observe o seguinte: "enquanto nada acontecer no seu loop" ...

O que acontece se uma operação que leva 5ms ocorrer a 99ms ...? Seu próximo disparo será atrasado até 104ms. Isso é uma deriva. Mas é fácil de combater. Em vez de dizer "O tempo gravado é agora", você diz "O tempo gravado é 100 ms depois do que era". Isso significa que, independentemente dos atrasos no código, o acionamento será sempre em intervalos de 100 ms ou será desviado dentro de um tick de 100 ms.

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

Agora você terá disparos em 100ms, 200ms, 300ms, etc. Ou, se houver atrasos em outros bits de código, você poderá obter 100ms, 204ms, 300ms, 408ms, 503ms, 600ms, etc. o intervalo possível, independentemente dos atrasos. E se você tiver atrasos maiores que o intervalo, ela executará automaticamente sua rotina vezes suficientes para acompanhar a hora atual.

Antes de você se desviar . Agora você tem instabilidade .


1

Porque você redefine o cronômetro após a operação.

static unsigned long PrevTime=millis();

unsigned long t = millis();

if (t - PrevTime > time) {
    foo();
    PrevTime = t;
}

não. observe que o PrevTime é estático.
Dannyf 4/12

4
@dannyf, sim e?
Juraj

se você soubesse o que "estático" significa, saberia por que sua resposta não está correta.
dannyf

Eu sei o que a estática faz com a variável local. Não entendo por que você acha que minha resposta tem algo a ver com a estática. Acabei de mover a leitura atual de milis antes que foo () seja chamado.
Juraj

-1

Para o que você está tentando fazer, delay () é a maneira apropriada de implementar o código. O motivo pelo qual você deseja usar o if (millis ()) é se você deseja permitir que o loop principal continue em loop, para que seu código ou outro código fora desse loop possa executar outro processamento.

Por exemplo:

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();
   }
}

Isso executaria foo () no intervalo especificado, permitindo que o loop continuasse sendo executado no meio. Eu coloquei o cálculo de next_trigger_time antes da chamada para foo () para ajudar a minimizar a deriva, mas é inevitável. Se a deriva for uma preocupação significativa, use um cronômetro de interrupção ou algum tipo de sincronização de relógio / cronômetro. Lembre-se também de que millis () será resolvido após algum período de tempo e eu não expliquei isso para manter o exemplo de código simples.


Odeio mencionar isso: problema de rolagem em 52 dias.

Eu já mencionei a questão de rolagem no final da minha resposta.
ThatAintWorking

Bem, resolva isso.

Minha taxa de consultoria padrão é de US $ 100 / h, se você quiser que eu escreva um código para você. Eu acho que o que escrevi é suficientemente relevante.
ThatAintWorking

11
Você está ciente de que Majenko postou uma resposta mais completa e melhor que a sua? Você está ciente de que seu código não é compilado? Isso long m - millis()não faz o que você pretende fazer? Isso é por conta da casa.

-4

seu código está correto.

o problema com o qual você se depara é com millis (): ele subconta ligeiramente (a subconta máxima é apenas 1ms, por invocação).

a solução é com ticks mais finos, como micros () - mas isso também subconta um pouco.


2
Por favor, forneça alguma evidência ou alguma referência para " ele subconta um pouco ".
Edgar Bonet
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.