Я реализую протокол через последовательные порты в Linux. Протокол основан на схеме ответа на запрос, поэтому пропускная способность ограничена временем, которое требуется для отправки пакета на устройство и получения ответа. Устройства в основном основаны на оружии и работают под Linux> = 3.0. У меня проблемы с сокращением времени прохождения сигнала ниже 10 мс (115200 бод, 8 бит данных, без проверки четности, 7 байт на сообщение).
Какие интерфейсы ввода-вывода дадут мне минимальную задержку: выбор, опрос, epoll или опрос вручную с помощью ioctl? Влияет ли блокирующий или неблокирующий ввод-вывод на задержку?
Я попытался установить флаг low_latency с помощью setserial. Но казалось, что это не имело никакого эффекта.
Могу ли я попытаться уменьшить время ожидания? Так как я контролирую все устройства, можно было бы даже исправить ядро, но это не так.
—- Редактировать —-
Последовательный контроллер использует 16550A.
Схемы запроса / ответа, как правило, неэффективны, и они быстро обнаруживаются на последовательном порту. Если вы заинтересованы в производительности, посмотрите на оконный протокол, например, протокол отправки файлов kermit.
Теперь, если вы хотите придерживаться своего протокола и уменьшить задержку, выберите, опросите, прочитайте — все это даст примерно одинаковую задержку, потому что, как указал Энди Росс, реальная задержка заключается в аппаратной обработке fifo.
Если вам повезет, вы можете настроить поведение драйвера без исправлений, но вам все равно нужно взглянуть на код драйвера. Однако наличие ARM для обработки частоты прерывания 10 кГц, безусловно, не будет хорошим для общей производительности системы …
Другой вариант — заполнить ваш пакет так, чтобы вы каждый раз достигали порога fifo. Это также подтвердит, если это проблема порогового значения fifo или нет.
10 мс @ 115200 достаточно для передачи 100 байт (при условии 8N1), поэтому, вероятно, вы видите, что флаг low_latency не установлен. Пытаться
setserial /dev/<tty_name> low_latency
Он установит флаг low_latency, который используется ядром при перемещении данных вверх в слой tty:
void tty_flip_buffer_push(struct tty_struct *tty)
{
unsigned long flags;
spin_lock_irqsave(&tty->buf.lock, flags);
if (tty->buf.tail != NULL)
tty->buf.tail->commit = tty->buf.tail->used;
spin_unlock_irqrestore(&tty->buf.lock, flags);
if (tty->low_latency)
flush_to_ldisc(&tty->buf.work);
else
schedule_work(&tty->buf.work);
}
Вызов schedule_work может отвечать за задержку в 10 мс, которую вы наблюдаете.
Последовательные порты в linux «обернуты» в терминальные конструкции в стиле Unix, что дает вам 1 такт, то есть 10 мс. Попробуй если stty -F /dev/ttySx raw low_latency
помогает, никаких гарантий, хотя.
На ПК вы можете пойти в хардкор и поговорить напрямую со стандартными последовательными портами, выпустить setserial /dev/ttySx uart none
отсоединить драйвер linux от последовательного порта hw и управлять портом через inb/outb
портировать регистры. Я пробовал, это прекрасно работает.
Недостатком является то, что вы не получаете прерываний при поступлении данных, и вам нужно опросить регистр. довольно часто.
Вы должны быть в состоянии сделать то же самое на стороне устройства руки, может быть намного сложнее на экзотическом последовательном порту hw.
Поговорив с еще несколькими инженерами на эту тему, я пришел к выводу, что эта проблема не решаема в пространстве пользователя. Поскольку нам нужно пересечь мост в землю ядра, мы планируем реализовать модуль ядра, который сообщает наш протокол и дает нам задержки < 1мс.
— редактировать —
Оказывается, я был совершенно неправ. Все, что было необходимо, это увеличить частоту тиков ядра. 100 тиков по умолчанию добавили задержку 10 мс. 1000 Гц и отрицательное приятное значение для последовательного процесса дает мне временное поведение, которого я хотел достичь.
Ни один из этих системных вызовов не влияет на задержку. Если вы хотите читать и записывать один байт как можно быстрее из пользовательского пространства, вы действительно не добьетесь большего успеха, чем простой read()/write()
пара. Попробуйте заменить последовательный поток сокетом из другого процесса пользовательского пространства и посмотрите, улучшатся ли задержки. Если это не так, то ваши проблемы связаны с частотой процессора и аппаратными ограничениями.
Вы уверены, что ваше оборудование может сделать это вообще? Нередко встречаются UART с буферным дизайном, который вводит задержку в несколько байтов.
На этих скоростях линии вы не должны видеть слишком большие задержки, независимо от того, как вы проверяете готовность.
Необходимо убедиться, что последовательный порт находится в режиме raw (поэтому вы выполняете «неканоническое чтение») и что VMIN и VTIME установлены правильно. Вы хотите убедиться, что VTIME равен нулю, чтобы межсимвольный таймер никогда не срабатывал. Вероятно, я бы начал с установки VMIN в 1 и настройки оттуда.
Затраты на системный вызов ничто по сравнению с временем в проводе, поэтому select () против poll () и т. Д. Вряд ли будет иметь значение.
Вот что setserial
устанавливает низкую задержку для файлового дескриптора порта:
ioctl(fd, TIOCGSERIAL, &serial);
serial.flags |= ASYNC_LOW_LATENCY;
ioctl(fd, TIOCSSERIAL, &serial);
Вкратце: используйте USB-адаптер и ASYNC_LOW_LATENCY.
Я использовал USB-адаптер FT232RL на Modbus со скоростью 115,2 кбит / с. Я получаю около 5 транзакций (на 4 устройства) в общей сложности около 20 мс с помощью ASYNC_LOW_LATENCY. Это включает в себя две транзакции для устройства с медленным касанием (время отклика 4 мс). Без ASYNC_LOW_LATENCY общее время составляет около 60 мс. С адаптерами FTDI USB ASYNC_LOW_LATENCY устанавливает межсимвольный таймер на самом чипе на 1 мс (вместо значения по умолчанию 16 мс). В настоящее время я использую USB-адаптер домашнего производства и могу установить задержку для самого адаптера на любое значение, которое я хочу. Установка его на 200 мкС сбрасывает еще одну мс с этих 20 мс.