2010-05-23 109 views
6

我正在开发一个项目,发送串行数据来控制LED灯的动画,这需要与动画引擎保持同步。似乎有一个大的串行写入缓冲区(OSX(POSIX)+ FTDI芯片组usb串行设备),所以如果不手动调节write()的调用,软件可以在灯光前几秒钟。串口:write()限制?

目前我手动将串行写入速度限制为波特率(8N1 = 10字节串行帧每8字节数据,19200bps串行 - > 1920字节每秒最大),但我遇到了动画问题随着时间流逝与灯光不同步 - 它开始很好,但在10分钟后,动画和灯光之间出现明显的(100ms +)延迟。

这是真实限制串行写入速度的代码(每个动画帧调用一次,“经过”是当前帧,“波特率”的持续时间是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); 
     } 
    } 
} 

很明显,某处出了问题。

一个更好的方法是能够确定当前在传输队列中的字节数,并尝试将其保持在固定阈值以下,但我无法弄清楚如何在OSX上执行此操作( POSIX)系统。

任何意见赞赏。

+1

跨缓冲区同步是POSIX通常不适合处理的东西;在所有消费者操作系统上同步音频和视频的早期尝试是多么糟糕的一个很好的例子。您可能需要直接写入串行UART或查找或写入允许准同步ioctl的驱动程序,例如“不会早于时间发出此字节_n_ – msw 2010-05-23 15:07:30

+0

您是否可以控制动画引擎? – 2010-05-23 15:12:09

+0

动画帧率是否固定? – 2010-05-23 16:02:39

回答

3

如果你想减慢你的动画以匹配你可以写入LED的最大速度,你可以使用tcdrain();像这样:

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

这就是我一直在寻找的!非常感谢。 – damian 2010-05-24 08:58:09

2

您可以使用硬件流量控制。

我不知道你在串口链路的另一端有什么样的硬件,但是通过RTS/CTS握手线可以同步和调节自己的硬件。

这就是他们打算的毕竟。

+0

我正在将UART输出直接插入一个RS485线路驱动器,因此没有机会。 – damian 2010-05-23 19:01:50

1

只要保持一个固定的波特率比它所需要稍微快一点,并同步发光二极管与动画N个动画帧的每个块:

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

的稍快的波特率将确保串行缓冲区不会逐渐溢出。

串行数据块之间会有一个小的停顿(毫秒),但这不应该被察觉。

编辑:这是假设你有一个固定的动画率。

+0

+1。通过您的解决方案,实际波特率是无关紧要的,除非它很慢,并且OP假定_serial port_是问题 - 它可能是_animation frame rate_。另一种可能性是UAR T时钟:第一次谷歌命中'rs232时钟准确度'表明时钟可以在温度和寿命上漂移+/- 0.5%,这与OP的1%漂移没有太大差别。 – 2010-05-23 16:55:17

+0

动画帧率不固定。我有时会闪烁所有的LED,有时会闪烁一个很好的淡入淡出 - 较低的帧率适合闪烁(因为串行数据包较大,所以必要),但对于单脉冲,我更喜欢更高的帧率以获得更好的视觉淡化。 – damian 2010-05-23 18:56:58

2

不得不将数据馈送到串行热图纸记录器一次(非常像收据打印机)并且具有相同类型的问题。数据的任何延迟都会导致打印输出中的跳过,这是不可接受的。

解决方案非常简单:如果您始终将数据保存在内核串行缓冲区中,那么输出将精确地(波特率/(1 +数据位+停止位))每秒字符。因此,只需添加足够的NUL字节填充以分隔数据。

如果某些设备在数据中看到NUL字节,那么它可能会做非常糟糕的事情,在这种情况下,这不起作用。但是很多只是忽略消息之间的额外NUL字节,这使您可以使用串口内非常准确的硬件定时器来控制您的定时。

+0

呵呵。好的想法,谢谢! 我正在通过一个RS485线路驱动器(半双工)推送数据,虽然目前没有必要从从属设备的ACK消息;但如果这种变化(如果我需要开始做错误检查,例如)这种方法将无法正常工作.. – damian 2010-05-23 18:55:15

+0

我的打印机实际上确实有一个反向通道与状态信息(不完全确认/ NACK,但我不明白这是如何改变任何东西)。如果你正在写另一端,那么在允许填充的同时你应该没有任何困难实现ACK/NACK。 – 2010-05-23 21:29:23

0

下面是一个方法,使用多线程,这是比我的其他回答不同:

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

在上述伪代码中,队列是阻塞producer-consumer queue,其中一个的容量。换句话说,生产者将在queue.push()期间阻塞,而队列不为空。您可以使用带有条件变量或信号量的“ping-pong”缓冲区来代替阻塞队列。

每个动画帧都会在发送相应的LED数据后显示。串行端口传输数据包所用的时间用于计算下一个动画帧。

拥有两条线程的好处是,您可以在等待串行数据传输(传输串行数据几乎不使用任何CPU)时使用CPU进行动画制作。

很难用单词来描述这种多线程的东西。我希望我有一个白板可以涂写。 :-)