我正在开发一个项目,发送串行数据来控制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) )系统。
任何建议表示赞赏。
答案 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)。
很难用单词来描述这种多线程的东西。我希望我有一块白板可以随意涂鸦。 : - )