Серийный номер: write () дросселирование? - PullRequest
6 голосов
/ 23 мая 2010

Я работаю над проектом, отправляющим последовательные данные для управления анимацией светодиодов, которые должны синхронизироваться с движком анимации. Похоже, существует большой буфер последовательной записи (последовательное USB-устройство с набором микросхем OSX (POSIX) + FTDI), поэтому без ручного регулирования вызовов write () программа может опередить свет на несколько секунд.

В настоящее время я вручную ограничиваю скорость последовательной записи скоростью передачи данных (8N1 = 10 байт последовательного кадра на 8 байт данных, 19200 бит / с последовательного -> 1920 байт в секунду макс), но у меня проблема с дрейфом анимации со временем не синхронизируется со светом - все запускается нормально, но через 10 минут между анимацией и светом заметно (100 мс +) лаг.

Это код, который ограничивает скорость последовательной записи (вызывается один раз для кадра анимации, «истекший» - длительность текущего кадра, «скорость передачи» - бит / с (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 ]

3 голосов
/ 24 мая 2010

Если вы хотите замедлить анимацию до максимальной скорости, которую вы можете записать на светодиоды, вы можете просто использовать tcdrain();как то так:

while (1)
{
    write(serial_fd, led_command);
    animate_frame();
    tcdrain(serial_fd);
}
2 голосов
/ 23 мая 2010

Пришлось один раз подать данные на серийный регистратор тепловых карт (очень похоже на чековый принтер) и возникли те же проблемы. Любые задержки в данных приводили к пропуску печатного вывода, что недопустимо.

Решение очень простое: если вы постоянно храните данные в последовательном буфере ядра, то выходной сигнал будет точно (скорость передачи / (1 + бит данных + стоп-бит)) символов в секунду. Так что просто добавьте достаточно NUL-байтовых отступов для разметки ваших данных.

Некоторые устройства могут делать очень плохие вещи, если они видят NUL-байты в данных, я полагаю, и в этом случае это не будет работать. Но многие просто игнорируют дополнительные байты NUL между сообщениями, что позволяет вам использовать очень точный аппаратный таймер внутри последовательного порта для управления временем.

2 голосов
/ 23 мая 2010

Вы можете использовать аппаратное управление потоком.

Я не знаю, какое у вас оборудование на другой стороне последовательного канала, но стороны кабины могут синхронизироваться и регулировать скорость через линии RTS / CTS.

Это то, для чего они предназначены.

1 голос
/ 23 мая 2010

Просто сохраните фиксированную скорость передачи данных, которая немного выше требуемой, и синхронизируйте светодиоды с анимацией для каждого блока из N кадров анимации:

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

Немного более высокая скорость передачи обеспечитпоследовательный буфер постепенно не переполняется.

Между блоками последовательных данных (миллисекунды) будет небольшая пауза, но это не должно быть ощутимо.фиксированная скорость анимации.

0 голосов
/ 24 мая 2010

Вот подход, использующий многопоточность, который отличается от моего другого ответа:

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

В приведенном выше псевдокоде очередь представляет собой блокирующую очередь производителя-потребителя с емкостью один. Другими словами, производитель будет блокироваться во время queue.push (), пока очередь не пуста. Вместо очереди блокировки вы также можете использовать буфер " ping-pong " с условной переменной или семафором.

Каждый кадр анимации отображается после передачи соответствующих светодиодных данных. Истекшее время, которое требуется последовательному порту для передачи пакета, используется для вычисления следующего кадра анимации.

Преимущество наличия двух потоков состоит в том, что вы можете использовать ЦП для анимации во время ожидания передачи последовательных данных (для передачи последовательных данных практически не используется ЦП).

Трудно описать эту многопоточность только словами. Я бы хотел, чтобы у меня была доска для рисования. : -)

...