串口:write()限制?

时间:2010-05-23 14:26:25

标签: c++ c synchronization serial-port posix

我正在开发一个项目,发送串行数据来控制LED灯的动画,这需要与动画引擎保持同步。似乎有一个大的串行写缓冲区(OSX(POSIX)+ FTDI芯片组usb串行设备),因此无需手动限制对write()的调用,软件可以提前几秒钟点亮。

目前我手动将串行写入速度限制为波特率(8N1 =每8字节数据10字节串行帧,19200 bps串行 - >每秒最多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) )系统。

任何建议表示赞赏。

5 个答案:

答案 0 :(得分:3)

如果要降低动画速度以匹配可以写入LED的最大速度,可以使用tcdrain();像这样的东西:

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

答案 1 :(得分:2)

您可以使用硬件流控制。

我不知道您在串行链路的另一端有什么样的硬件,但是展台两侧可以通过RTS / CTS握手线同步并节约自己。

这毕竟是他们想要的。

答案 2 :(得分:2)

必须将数据提供给串行热带图表记录器一次(非常像收据打印机)并且遇到同样的问题。数据的任何延迟都会导致打印输出中的跳过,这是不可接受的。

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

如果某些设备在我认为的数据中看到NUL字节,那么它们可能会做很糟糕的事情,在这种情况下,这将无效。但是许多人只是忽略了消息之间的额外NUL字节,这使您可以使用串行端口内非常精确的硬件定时器来控制时序。

答案 3 :(得分:1)

只需保持一个比它需要的速度略快的固定波特率,并为每个N帧动画帧的块同步LED和动画:

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

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

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

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

答案 4 :(得分: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,容量为1。换句话说,生成器将在queue.push()期间阻塞,而队列不为空。您也可以使用带有条件变量或信号量的“ping-pong”缓冲区而不是阻塞队列。

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

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

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