Posso rendere più preciso il ritardo dei microsecondi?


8

Sto provando a bit bang dati DMX e questo richiede 4 impulsi. Non avendo molta fortuna con i risultati, sto verificando per vedere quanto è bravo l'Arduino a ritardare ... Sembra essere abbastanza terribile.

Ecco un breve test che ho fatto:

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 i risultati:

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

Sono rimasto un po 'scioccato da quanto sia grave la sua precisione. È il doppio del tempo che volevo ritardare, ma non è nemmeno coerente con il punto in cui avrei potuto dividere per 2!

C'è qualcosa che posso fare per ottenere risultati corretti e coerenti?


Perché stai sbattendo i bit invece di accedere direttamente all'UART?
— Ignacio Vazquez-Abrams,

Quindi posso avere più di un output.
— bwoogie,

Il Mega ha quattro UART. Anche se ne hai uno in modo permanente per la programmazione che ti dà ancora tre universi.
— Ignacio Vazquez-Abrams,

Sto solo testando con il mega perché è quello che ho attualmente disponibile, il progetto finale avrà un ATMEGA328
— bwoogie

1
Il problema qui è come si misura.
— Gerben,

Risposte:


7

Come spiegato nelle risposte precedenti, il tuo vero problema non è l'accuratezza di delayMicroseconds(), ma piuttosto la risoluzione di micros().

Tuttavia, per rispondere alla tua domanda effettiva , esiste un'alternativa più accurata a delayMicroseconds(): la funzione _delay_us()di AVR-libc è accurata al ciclo e, ad esempio

_delay_us(1.125);

fa esattamente quello che dice. L'avvertenza principale è che l'argomento deve essere una costante di compilazione-tempo. Devi avere per #include <util/delay.h>avere accesso a questa funzione.

Si noti inoltre che è necessario bloccare gli interrupt se si desidera qualsiasi tipo di ritardo preciso.

Modifica : Ad esempio, se dovessi generare un impulso di 4 µs su PD2 (pin 19 su Mega), procederei come segue. Innanzitutto, nota che il seguente codice

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

emette un impulso lungo di 0,125 µs (2 cicli CPU), poiché è il tempo necessario per eseguire l'istruzione che imposta la porta LOW. Quindi, aggiungi il tempo mancante in ritardo:

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

e hai una larghezza di impulso accurata al ciclo. Vale la pena notare che non è possibile ottenere questo risultato digitalWrite(), poiché una chiamata a questa funzione richiede circa 5 µs.


3

I risultati del test sono fuorvianti. delayMicroseconds()ritardi effettivamente abbastanza precisi (per ritardi superiori a 2 o 3 microsecondi). Puoi esaminare il suo codice sorgente nel file /usr/share/arduino/hardware/arduino/cores/arduino/wiring.c (su un sistema Linux; o su un percorso simile su altri sistemi).

Tuttavia, la risoluzione di micros()è di quattro microsecondi. (Vedi, ad esempio, la pagina di garretlab sumicros() .) Quindi, non vedrai mai una lettura tra 4 microsecondi e 8 microsecondi. Il ritardo effettivo potrebbe essere di pochi cicli nell'arco di 4 microsecondi, ma il codice lo segnalerà come 8.

Prova a fare 10 o 20 delayMicroseconds(4);chiamate di fila (duplicando il codice, non usando un ciclo) e quindi segnala il risultato di micros().


Con 10 ritardi di 4 sto ottenendo una miscela di 32 e 28 ... ma 4 * 10 = 40.
— bwoogie

Cosa ottieni, diciamo, 10 ritardi di 5? :) Si noti inoltre che per il bitbang potrebbe essere necessario utilizzare accessi alle porte abbastanza diretti, vale a dire no digitalWrite(), il che richiede diversi microsecondi per l'esecuzione.
— James Waldby - jwpat7,

40 e 44 ... Ma non dovrebbe essere arrotondato per eccesso? Cosa mi sto perdendo qui?
— bwoogie,

Per "arrotondamento" intendi delayMicroseconds()? Non lo considero migliore dell'arrotondamento. ¶ Per quanto riguarda la fonte di inesattezza, se la routine viene allineata, i tempi dipendono dal codice circostante. È possibile leggere elenchi di assemblaggio o disassemblaggio per vedere. (Vedi la sezione "Creazione di elenchi di assiemi" nella mia risposta alla domanda equivalente per PORTB in Arduino Mega 2560 , che può comunque essere rilevante per il tuo progetto di bitbanging
— James Waldby - jwpat7,

2

Sto controllando per vedere quanto è bravo Arduino a ritardare ... Sembra essere abbastanza terribile.

micros()ha una risoluzione ben documentata di 4 µs.

È possibile migliorare la risoluzione modificando il prescaler per Timer 0 (ovviamente che getta via le cifre, ma è possibile compensare quello).

In alternativa, utilizzare Timer 1 o Timer 2 con un prescaler di 1, che offre una risoluzione di 62,5 ns.


 Serial.begin(9600);

Sarà comunque lento.


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

L'output è esattamente coerente con la risoluzione di 4 µs micros()accoppiata al fatto che a volte si otterrebbero due "tick" e a volte uno, a seconda di esattamente quando è iniziato il timing.


Il tuo codice è un esempio interessante di errore di misurazione. delayMicroseconds(4);ritarderà per quasi 4 µs. Tuttavia, i tuoi tentativi di misurarlo sono imputabili.

Inoltre, se si verifica un interrupt, allungherà un po 'l'intervallo. È necessario disattivare gli interrupt se si desidera un ritardo esatto.


1

Quando misurato con un oscilloscopio, ho scoperto che:

delayMicroseconds(0)= delayMicroseconds(1)= 4 μs di ritardo reale.

Quindi, se si desidera un ritardo di 35 μs è necessario:

delayMicroseconds(31);

0

C'è qualcosa che posso fare per ottenere risultati corretti e coerenti?

L'implementazione di Arduino è piuttosto generica, quindi potrebbe non essere altrettanto efficace in alcune applicazioni. Ci sono alcuni modi per brevi ritardi, ognuno con le proprie carenze.

  1. Usa no. Ognuna è un'istruzione così 16 di noi.

  2. Utilizzare direttamente tcnt0. Ciascuno è 4us in quanto il prescaler è impostato su 64. È possibile modificare il prescaker per ottenere la risoluzione 16 °.

  3. Usa le zecche, puoi implementare un clone di systick e usarlo come base del ritardo. Offre una risoluzione più fine e precisione.

modificare:

Ho usato il seguente blocco per cronometrare i vari approcci:

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

prima di allora, avevo ripristinato il prescaler timer0 a 1: 1, quindi ogni tick TCNT0 è 1/16 di un microsecondo.

  1. delay4us () è costruito da NOP (); ha prodotto un ritardo di 65 tick, o poco più di 4us;
  2. t0delayus () è costituito dai ritardi timer0. ha prodotto un ritardo di 77 tick; leggermente peggio di delay4us ()
  3. _delay_us () è una funzione gcc-avr. prestazione alla pari con delay4us ();
  4. delayMicroseconds () ha prodotto un ritardo di 45 tick. il modo in cui arduino ha implementato le sue funzioni di temporizzazione, tende a sottovalutare, a meno che in un ambiente con altri interrupt.

spero che sia d'aiuto.


Si noti che il risultato previsto è 65 tick, non 64, poiché la lettura TCNT0richiede 1 ciclo della CPU.
— Edgar Bonet,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.