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.