2010-05-23 7 views
6

Sto lavorando a un progetto che invia dati seriali per controllare l'animazione di luci a LED, che devono rimanere sincronizzati con un motore di animazione. Sembra esserci un grande buffer di scrittura seriale (dispositivo seriale USB chipset OSX (POSIX) + FTDI), quindi senza limitare manualmente le chiamate a write(), il software può ottenere diversi secondi prima delle luci.Seriale: write() throttling?

Attualmente mi sto limitando manualmente la velocità di scrittura seriale alla velocità di trasmissione (telaio di serie 8N1 = 10 byte per byte di dati 8, 19200 bps seriali -> 1920 byte al secondo max), ma io sto avendo un problema con l'animazione alla deriva fuori sincrono con le luci nel tempo - inizia bene, ma dopo 10 minuti c'è un ritardo notevole (100ms +) tra l'animazione e le luci.

Questo è il codice che limitano la velocità di scrittura seriale (chiamato una volta per frame di animazione, 'trascorso' è la durata del frame corrente, 'baud' è il bps (19200)):

void BufferedSerial::update(float elapsed) 
{ 
    baud_timer += elapsed; 

    if (bytes_written > 1024) 
    { 
     // maintain baudrate 
     float time_should_have_taken = (float(bytes_written)*10)/float(baudrate); 
     float time_actually_took = baud_timer; 
     // sleep if we have > 20ms lag between serial transmit and our write calls 
     if (time_should_have_taken-time_actually_took > 0.02f) 
     { 
      float sleep_time = time_should_have_taken - time_actually_took; 
      int sleep_time_us = sleep_time*1000.0f*1000.0f; 
      //printf("BufferedSerial::update sleeping %i ms\n", sleep_time_us/1000); 
      delayUs(sleep_time_us); 

      // subtract 128 bytes 
      bytes_written -= 128; 
      // subtract the time it should have taken to write 128 bytes 
      baud_timer -= (float(128)*10)/float(baudrate); 
     } 
    } 
} 

Chiaramente c'è qualcosa che non va, da qualche parte.

Un approccio molto migliore sarebbe quello di essere in grado di determinare il numero di byte attualmente nella coda di trasmissione, e cercare di mantenerlo al di sotto di una soglia fissa, ma non riesco a capire come farlo su un OSX (POSIX).

Qualsiasi consiglio è gradito.

+1

La sincronizzazione tra i buffer è qualcosa che POSIX è generalmente inadatto a gestire; i primi tentativi di audio e video sincronizzati su tutti i sistemi operativi dei consumatori sono un buon esempio di quanto male. Potrebbe essere necessario scrivere direttamente alla UART seriale o trovare o scrivere un driver che permetta l'ioctli plesiocrono come "emettere questo byte non prima del tempo _n_ – msw

+0

Hai il controllo del motore di animazione? –

+0

Il framerate dell'animazione è fisso? –

risposta

3

Se si desidera rallentare l'animazione in modo che corrisponda alla velocità massima che è possibile scrivere sui LED, è sufficiente utilizzare tcdrain(); qualcosa di simile:

while (1) 
{ 
    write(serial_fd, led_command); 
    animate_frame(); 
    tcdrain(serial_fd); 
} 
+0

questo è quello che stavo cercando! grazie mille. – damian

2

È possibile utilizzare il controllo del flusso hardware.

Non so che tipo di hardware hai sull'altro lato del collegamento seriale, ma i lati della cabina potrebbero sincronizzarsi e regolarsi tramite le linee di handshake RTS/CTS.

Questo è ciò a cui sono destinati, dopo tutto.

+0

sto spingendo l'uscita UART direttamente in un driver di linea RS485, quindi non c'è alcuna possibilità di farlo .. – damian

1

Basta tenere una velocità di trasmissione fissa che è leggermente più veloce di quello che deve essere, e sincronizzare i LED con l'animazione per ogni blocco di N fotogrammi di animazione:

for each block 
{ 
    writeBlockSerialData(); 
    for each frame in block 
    { 
     animateFrame(); 
    } 
} 

La velocità di trasmissione leggermente più veloce farà in modo che la il buffer seriale non trabocca gradualmente.

Ci sarà una piccola pausa tra i blocchi di dati seriali (millisecondi) ma questo non dovrebbe essere percepibile.

MODIFICA: si presume che si disponga di una velocità di animazione fissa.

+0

+1. Con la tua soluzione, l'attuale il baud rate è irrilevante, a meno che non sia _too_ slow. E l'OP presuppone che la _serial port_ sia il problema - potrebbe essere il frame rate di virgolette_. Un'altra possibilità è l'UAR T clock: il primo hit di Google per 'rs232 clock accuracy' indica che gli orologi possono andare alla deriva di +/- 0,5% rispetto alla temperatura e alla durata, che non è molto diversa dalla deriva dell'1% dell'OP. –

+0

La frequenza fotogrammi dell'animazione non è fissa. A volte capita di sfarfallare tutti i LED ea volte pulsare con una bella dissolvenza - framerate inferiore va bene per il tremolio (e necessario poiché i pacchetti seriali sono più grandi), ma per il singolo impulso preferirei un framerate più alto per una dissolvenza visiva più gradevole. – damian

2

Si è dovuto inserire i dati su un registratore a banda termica seriale una volta (molto simile a una stampante di ricevute) e aveva lo stesso tipo di problemi. Qualsiasi ritardo nei dati ha causato salti nell'output stampato che sono inaccettabili.

La soluzione è molto semplice: se si conservano i dati nel buffer seriale del kernel in ogni momento, l'output sarà esattamente (velocità di trasmissione/(1 + bit di dati + stop di bit)) caratteri al secondo. Quindi basta aggiungere un numero sufficiente di byte pad NUL per distanziare i tuoi dati.

Alcuni dispositivi potrebbero fare molto male se vedono NUL byte nei dati, suppongo, nel qual caso questo non funzionerà. Ma molti ignorano semplicemente i byte NUL extra tra i messaggi, il che consente di utilizzare il timer hardware molto accurato all'interno della porta seriale per controllare i tempi.

+0

eh. bel pensiero, grazie! sto spingendo i dati attraverso un driver di linea RS485 (half-duplex) e anche se al momento non c'è bisogno di messaggi ACK dai dispositivi slave; ma se questo cambia (se ho bisogno di iniziare a fare il controllo degli errori per esempio) questo metodo non funzionerà .. – damian

+0

La mia stampante aveva effettivamente un canale inverso con informazioni sullo stato (non precisamente ACK/NACK ma non vedo come cambia qualcosa). Se stai scrivendo l'altra estremità, non dovresti avere alcuna difficoltà nell'implementare ACK/NACK nello stesso momento in cui permetti il ​​riempimento. –

0

Ecco un approccio, utilizzando il multi-threading, che è diverso rispetto al mio altra risposta:

ledThread() 
{ 
    while(animating) 
    { 
     queue.pop(packet); 
     writeSerialPacket(packet); 
     flushSerialPacket(); // Blocks until serial buffer is empty 
    } 
} 

animationThread() 
{ 
    time lastFrameTime = now(); 
    time_duration elapsed = 0; 
    while(animating) 
    { 
     buildLedPacket(packet); 
     queue.push(packet); 
     elapsed = lastFrameTime - now(); 
     lastFrameTime = now(); 
     animateNextFrame(elapsed); 
    } 
} 

Nel pseudocodice sopra, la coda è un blocco producer-consumer queue, con una capacità di uno. In altre parole, il produttore bloccherà durante queue.push() mentre la coda non è vuota. Invece di una coda di blocco, è possibile utilizzare anche un buffer "ping-pong" con una variabile di condizione o semaforo.

Ogni frame di animazione viene visualizzato dopo aver trasmesso i dati LED corrispondenti. Il tempo trascorso che la porta seriale impiega per trasmettere un pacchetto viene utilizzato per calcolare il prossimo fotogramma dell'animazione.

Il vantaggio di avere due thread è che è possibile utilizzare la CPU per l'animazione mentre si attende la trasmissione dei dati seriali (la trasmissione di dati seriali non richiede praticamente alcuna CPU).

È difficile descrivere questo materiale multi-threading con solo parole. Vorrei avere una lavagna su cui scrivere. :-)