Posso tornar os delayMicrosegundos mais precisos?


8

Estou tentando bit bang dados DMX e isso requer 4us pulsos. Não tendo muita sorte com os resultados, estou verificando o quão bom o Arduino está atrasando ... Parece ser terrível nisso.

Aqui está um pequeno teste rápido que eu fiz:

unsigned long ptime;

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

void loop() {
  ptime = micros();
  delayMicroseconds(4);
  Serial.println(micros() - ptime);

  delay(1000); //just to keep the serial monitor from going nuts
}

E os resultados:

8 4 8 4 4 4 4 4 8 8 8 8 4 8 8 4 4 8 4 4 4 8 8 8 8 8 8 4

Fiquei meio chocado com o quão ruim é a precisão. É o dobro do tempo que eu queria adiar, mas nem mesmo é consistente com o que eu poderia dividir por 2!

Há algo que eu possa fazer para obter resultados corretos e consistentes?


Por que você está batendo pouco em vez de acessar o UART diretamente?
Ignacio Vazquez-Abrams

Para que eu possa ter mais de uma saída.
precisa saber é o seguinte

O Mega possui quatro UARTs. Mesmo se você mantiver uma permanentemente para programação, isso ainda lhe dará três universos.
Ignacio Vazquez-Abrams

Estou apenas testando com o mega porque é isso que eu tenho atualmente disponíveis, o projeto final terá um ATMEGA328
bwoogie

11
O problema aqui é como você mede.
precisa saber é o seguinte

Respostas:


7

Conforme explicado nas respostas anteriores, seu problema real não é a precisão de delayMicroseconds(), mas a resolução de micros().

No entanto, para responder à sua pergunta real , existe uma alternativa mais precisa delayMicroseconds(): a função _delay_us()do AVR-libc é precisa do ciclo e, por exemplo,

_delay_us(1.125);

Faça exatamente o que isso diz. A principal ressalva é que o argumento deve ser uma constante em tempo de compilação. Você precisa para #include <util/delay.h>ter acesso a esta função.

Observe também que você deve bloquear as interrupções se desejar algum tipo de atraso preciso.

Edit : Como um exemplo, se eu fosse gerar um pulso de 4 µs no PD2 (pino 19 no Mega), procederia da seguinte maneira. Primeiro, observe que o seguinte código

noInterrupts();
PORTD |= _BV(PD2);   // PD2 HIGH
PORTD &= ~_BV(PD2);  // PD2 LOW
interrupts();

produz um pulso de 0,125 µs (2 ciclos de CPU), porque é o tempo necessário para executar a instrução que define a porta LOW. Em seguida, adicione o tempo que falta em um atraso:

noInterrupts();
PORTD |= _BV(PD2);   // PD2 HIGH
_delay_us(3.875);
PORTD &= ~_BV(PD2);  // PD2 LOW
interrupts();

e você tem uma largura de pulso com precisão de ciclo. Vale a pena notar que isso não pode ser alcançado com digitalWrite(), pois uma chamada para esta função leva cerca de 5 µs.


3

Os resultados do seu teste são enganosos. delayMicroseconds()realmente atrasa com bastante precisão (para atrasos de mais de 2 ou 3 microssegundos). Você pode examinar seu código-fonte no arquivo /usr/share/arduino/hardware/arduino/cores/arduino/wiring.c (em um sistema Linux; ou em algum caminho semelhante em outros sistemas).

No entanto, a resolução de micros()é de quatro microssegundos. (Veja, por exemplo, a página do garretlab sobremicros() .) Portanto, você nunca verá uma leitura entre 4 microssegundos e 8 microssegundos. O atraso real pode demorar apenas alguns ciclos em 4 microssegundos, mas seu código o reportará como 8.

Tente fazer 10 ou 20 delayMicroseconds(4);chamadas seguidas (duplicando o código, não usando um loop) e depois relate o resultado de micros().


Com 10 atrasos de 4 Estou ficando uma mistura de 32 de e 28 de ... mas 4 * 10 = 40.
bwoogie

Com o que você ganha, digamos, 10 atrasos de 5? :) Observe também que, para bitbanging, você pode precisar usar acessos de porta bastante diretos, ou seja, não digitalWrite(), o que leva vários microssegundos para ser executado.
precisa saber é o seguinte

40 e 44 ... Mas não deveria ser arredondado? O que estou perdendo aqui?
precisa saber é o seguinte

Por "arredondar para cima", você quer dizer delayMicroseconds()? Não vejo isso melhor do que arredondar para baixo. ¶ Em relação à fonte de imprecisão, se a rotina for incorporada, o tempo depende do código circundante. Você pode ler as listagens de montagem ou desmontagem para ver. (Consulte “Efectuar a montagem listagens” seção na minha resposta à pergunta equivalente para PORTB em Arduino mega 2560 , o que pode mesmo assim ser relevante para o seu projecto bitbanging
James Waldby - jwpat7

2

Estou verificando o quão bom o Arduino está atrasando ... Parece ser terrível nisso.

micros()tem uma resolução bem documentada de 4 µs.

Você pode melhorar a resolução alterando o prescaler para o Timer 0 (é claro que mostra os números, mas você pode compensar isso).

Como alternativa, use o Temporizador 1 ou o Temporizador 2 com um pré-calibrador de 1, que fornece uma resolução de 62,5 ns.


 Serial.begin(9600);

Isso vai ser lento de qualquer maneira.


8 4 8 4 4 4 4 4 8 8 8 8 4 8 8 4 4 8 4 4 4 8 8 8 8 8 8 4

Sua saída é exatamente consistente com a resolução de 4 µs, micros()juntamente com o fato de que às vezes você recebe dois "ticks" e às vezes um, dependendo exatamente quando você iniciou o tempo.


Seu código é um exemplo interessante de erro de medição. delayMicroseconds(4);atrasará cerca de 4 µs. No entanto, suas tentativas de medi-lo são falhas.

Além disso, se ocorrer uma interrupção, isso aumentará um pouco o intervalo. Você precisa desativar as interrupções se desejar um atraso exato.


1

Quando medido com um osciloscópio, descobri que:

delayMicroseconds(0)= delayMicroseconds(1)= 4 μs de atraso real.

Portanto, se você quiser um atraso de 35 μs, precisará:

delayMicroseconds(31);

0

Há algo que eu possa fazer para obter resultados corretos e consistentes?

A implementação do Arduino é bastante genérica, portanto, pode não ser tão eficaz em alguns aplicativos. Existem algumas maneiras de atrasos curtos, cada um com seus próprios déficits.

  1. Use nop. Cada uma é uma instrução, portanto, 16 de nós.

  2. Use tcnt0 diretamente. Cada um tem 4us, pois o pré-calibrador está definido como 64. Você pode alterar o pré-seletor para alcançar a 16ª resolução.

  3. Use ticks, você pode implementar um clone de systick e usá-lo como base do atraso. Oferece resolução mais fina e precisão.

editar:

Usei o seguinte bloco para cronometrar as várias abordagens:

time0=TCNT0;
delay4us();             //65
//t0delayus(4*16);          //77
//_delay_us(4);             //65
//delayMicroseconds(4);     //45
time1=TCNT0 - time0;        //64 expected

antes disso, eu havia redefinido o pré-calibrador timer0 para 1: 1 para que cada marca de TCNT0 seja 1/16 de um microssegundo.

  1. delay4us () é criado a partir do NOP (); produziu um atraso de 65 ticks, ou pouco mais de 4us;
  2. t0delayus () é criado a partir de atrasos no timer0. produziu um atraso de 77 carrapatos; ligeiramente pior que delay4us ()
  3. _delay_us () é uma função gcc-avr. desempenho a par com delay4us ();
  4. delayMicroseconds () produziu um atraso de 45 ticks. da maneira como o arduino implementou suas funções de temporização, ele tende a sub-contar, a menos que esteja em um ambiente com outras interrupções.

espero que ajude.


Observe que o resultado esperado é de 65 tiques, não de 64, porque a leitura TCNT0leva 1 ciclo da CPU.
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.