Последовательный: записать () регулировку?

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

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

Это - код, это ограничивает последовательную скорость записи (названный однажды на кадр анимации, 'протек', продолжительность текущего кадра, 'baudrate' является бит/с (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) система.

Любой совет ценится.

6
задан dsolimano 17 October 2012 в 18:21
поделиться

5 ответов

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

while (1)
{
    write(serial_fd, led_command);
    animate_frame();
    tcdrain(serial_fd);
}
3
ответ дан 16 December 2019 в 21:35
поделиться

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

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

В конце концов, они для этого и предназначены.

2
ответ дан 16 December 2019 в 21:35
поделиться

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

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

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

2
ответ дан 16 December 2019 в 21:35
поделиться

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

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 » с условной переменной или семафором.

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

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

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

0
ответ дан 16 December 2019 в 21:35
поделиться

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

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

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

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

РЕДАКТИРОВАТЬ: Предполагается, что у вас фиксированная скорость анимации.

1
ответ дан 16 December 2019 в 21:35
поделиться