2010-05-23 8 views
6

Estoy trabajando en un proyecto que envía datos en serie para controlar la animación de luces LED, que deben estar sincronizadas con un motor de animación. Parece que hay un gran buffer serial de escritura (OSX (POSIX) + FTDI chipset usb serial device), por lo que sin estrangular manualmente las llamadas a write(), el software puede adelantarse varios segundos a las luces.Serial: write() throttling?

Actualmente estoy restringiendo manualmente la velocidad de escritura en serie a la velocidad de baudios (8N1 = 10 bytes en serie por 8 bytes de datos, 19200 bps en serie -> 1920 bytes por segundo como máximo), pero tengo un problema con la animación Sin sincronización con las luces a lo largo del tiempo, comienza bien, pero después de 10 minutos hay un desfase notable (100ms +) entre la animación y las luces.

Este es el código que se restringe la velocidad de escritura en serie (llamada una vez por cuadro animación, 'transcurrido' es la duración de la trama actual, 'velocidad de transmisión' es el 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); 
     } 
    } 
} 

Claramente, hay algo mal, en alguna parte.

Un enfoque mucho mejor sería poder determinar el número de bytes actualmente en la cola de transmisión, y tratar de mantenerlo por debajo de un umbral fijo, pero no puedo encontrar la manera de hacerlo en un OSX (Sistema POSIX).

Cualquier consejo apreciado.

+1

sincronización a través de buffers es algo que POSIX es generalmente mal adaptado para manejar; Los primeros intentos de audio y video sincronizados en todos los sistemas operativos de los consumidores son un buen ejemplo de lo malo que es. Puede que tenga que escribir en el UART de serie directamente o buscar o escribir un controlador que permita ioctls plesiócronas como "emitir este byte no antes de tiempo _n_. – msw

+0

¿Tiene control sobre el motor de animación? –

+0

¿Se ha corregido la velocidad de fotogramas de la animación? –

Respuesta

3

Si desea reducir la velocidad de su animación para que coincida con la velocidad máxima que puede escribir en los LED, puede simplemente usar tcdrain(); algo como esto:

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

eso es lo que estaba buscando! Muchas gracias. – damian

2

Puede usar el control de flujo de hardware.

No sé qué tipo de hardware tiene en el otro lado del enlace serial, pero los lados de la cabina podrían sincronizarse y acelerarse a través de las líneas de saludo RTS/CTS.

Para eso están destinados, después de todo.

+0

estoy impulsando la salida UART directamente en un controlador de línea RS485, por lo que no hay posibilidad de que ... – damian

1

Hemos de tener una velocidad de transmisión fija, que es ligeramente más rápido que lo que tiene que ser, y sincronizar los LEDs con la animación para cada bloque de cuadros de animación N:

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

La velocidad de transmisión ligeramente más rápido se asegurará de que la el buffer serial no se desborda gradualmente

Habrá una pequeña pausa entre los bloques de datos en serie (milisegundos) pero esto no debe ser perceptible.

EDITAR: Esto es suponiendo que tiene una tasa de animación fija.

+0

+1. Con su solución, el real la velocidad en baudios es irrelevante, a menos que sea demasiado lenta. Y el OP supone que el "puerto serial_" es el problema, podría ser la tasa de fotogramas de animación. Otra posibilidad es la UAR Reloj en T: el primer hit de Google para 'precisión del reloj rs232' indica que los relojes pueden derivar en +/- 0.5% sobre la temperatura y la vida, que no es muy diferente de la deriva del 1% del OP. –

+0

La velocidad de cuadros de la animación no es fija. A veces parpadeo todos los LED y a veces pulso uno con un fade agradable - menor framerate está bien para parpadear (y es necesario ya que los paquetes de serie son más grandes), pero para el pulso único preferiría un mayor framerate para un fade visual más agradable. – damian

2

Tuve que alimentar los datos a una grabadora de gráficos de tira térmica en serie una vez (muy parecido a una impresora de recibos) y tenía el mismo tipo de problemas. Cualquier retraso en los datos provocó saltos en la salida impresa que son inaceptables.

La solución es muy simple: si mantiene los datos en el búfer en serie del kernel en todo momento, entonces la salida será exactamente (velocidad en baudios/(1 + bits de datos + bits de parada)) caracteres por segundo. Así que solo agregue suficiente espacio de bytes NUL para espaciar sus datos.

Algunos dispositivos podrían hacer cosas muy malas si ven bytes NUL en los datos, supongo, en cuyo caso esto no funcionará. Pero muchos simplemente ignoran los bytes NUL adicionales entre los mensajes, lo que le permite usar el temporizador de hardware muy preciso dentro del puerto serie para controlar su tiempo.

+0

huh. buen pensamiento, gracias! Estoy presionando los datos a través de un controlador de línea RS485 (semidúplex) y aunque en este momento no hay necesidad de enviar mensajes ACK desde los dispositivos esclavos; pero si eso cambia (si necesito comenzar a hacer una comprobación de errores, por ejemplo) este método no funcionará. – damian

+0

Mi impresora realmente tenía un canal inverso con información de estado (no precisamente ACK/NACK, pero no veo cómo cambia cualquier cosa). Si escribe el otro extremo, entonces no debería tener ninguna dificultad para implementar ACK/NACK al mismo tiempo que permite el relleno. –

0

Aquí es un enfoque, utilizando multi-threading, que es diferente a mi otra respuesta:

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

En el pseudocódigo anterior, la cola es un bloqueo producer-consumer queue, con una capacidad de uno. En otras palabras, el productor bloqueará durante queue.push() mientras la cola no está vacía. En lugar de una cola de bloqueo, también puede usar un buffer "ping-pong" con una variable de condición o semáforo.

Cada cuadro de animación se muestra después de que se transmitieron los datos LED correspondientes. El tiempo transcurrido que tarda el puerto serie para transmitir un paquete se utiliza para calcular el siguiente cuadro de animación.

La ventaja de tener dos hilos es que puede usar la CPU para animar mientras espera que los datos en serie se transmitan (la transmisión de datos en serie casi no utiliza ninguna CPU).

Es difícil describir este material de subprocesos múltiples con solo palabras. Ojalá tuviera una pizarra para garabatear. :-)