![](https://habrastorage.org/storage2/49d/b51/976/49db5197690742564a949ab014dac3c1.png)
Введение
«Протокол точного времени» описан стандартом IEEE 1588. Существует 2 версии стандарта. Первая версия вышла в 2002 году, затем стандарт был пересмотрен в 2008 и на свет вышел протокол PTPv2. Обратная совместимость не была сохранена.
Я работаю со второй версией протокола, в ней множество улучшений по сравнению с первой (точность, стабильность, как нам сообщает wiki). Не буду приводить сравнения с NTP, одно только упоминание о точности синхронизации, а точность PTP достигает действительно десятков наносекунд при «железной» поддержке, говорит о преимуществе перед NTP.
«Железная» поддержка протокола в разных устройствах может быть реализована по-разному. На самом деле минимум, требующийся для реализации PTP – умение «железки» проставлять таймштамп момента получения сообщения на порт. Проставленное время будет использовано для вычисления ошибки.
Почему часы расстраиваются?
Ошибки могут появиться отовсюду. Начнем с того, что генераторы частоты в устройствах разные и очень мала вероятность того, что два разных устройства будут работать идеально такт в такт. Тут же можно приписать постоянно меняющиеся условия окружающей среды, влияющие на генерируемую частоту.
Чего мы добиваемся?
Допустим, у нас есть устройство, работающее в идеальных условиях, какие-нибудь атомные часы, которые вообще не разойдутся до конца света (конечно же, до реального, а не предначертанного календарём Майя) и дана задача заполучить хотя бы примерно (с точностью до 10-9сек) такие же часы. Нам нужно эти часы синхронизировать. Для этого можно реализовать протокол PTP.
Разница чисто программной реализации и реализации с «железной поддержкой»
Чисто программная реализация не позволит добиться обещаемой точности. Время, прошедшее с момента получения сообщения (точнее получения сигнала на прием сообщения в устройстве) до перехода на точку входа в прерывание или на callback не может быть строго определенным. «Умные железки» с поддержкой PTP умеют проставлять эти таймштампы самостоятельно (например, чипы от Micrel, как раз для KSZ8463MLI я пишу драйвер).
Помимо таймштампов к «железной» поддержке также можно отнести умение настраивать кварцевый генератор (чтобы выровнять частоту с мастером), либо возможность подстройки часов (каждый такт увеличивать значение часов на X нс). Об этом ниже.
Перейдём к стандарту IEEE 1588
Стандарт описан аж на 289 страницах. Рассмотрим минимум, необходимый для реализации протокола. PTP является клиент-серверным протоколом синхронизации, т.е. для реализации протокола требуется как минимум 2 устройства. Итак, Master-устройство — атомные часы, а Slave устройство – часы, которые необходимо заставить работать точно.
Язык обмена
Announce message – сообщение анонса, содержит информацию, отправляемую мастером всем Slave устройствам. Slave устройство с помощью этого сообщения может выбрать лучшего мастера (для этого существует BMC (Best Master Clock)) алгоритм. BMC не так интересен. Этот алгоритм можно легко найти в стандарте. Выбор идет по таким полям сообщения как точность, дисперсия, класс, приоритет и т.п. Перейдём к другим сообщениям.
Sync/Follow Up, DelayResp, PDelayResp/PDelayFollowUp – отправляются мастером, ниже рассмотрим их поподробнее.
DelayReq, PDelayReq – запросы Slave устройств.
Как видим, Slave устройство не многословно, Master предоставляет всю практически всю информацию сам. Отправка осуществляется на Multicast (при желании можно использовать Unicast режим) адреса, строго определенные в стандарте. Для PDelay сообщений имеется отдельный адрес (01-80-C2-00-00-0E для Ethernet и 224.0.0.107 для UDP). Остальные сообщения отсылаются на 01-1B-19-00-00-00 или 224.0.1.129. Пакеты отличаются полями ClockIdentity (идентификатор часов) и SequenceId (идентификатор пакета).
Сеанс работы
Допустим, мастер был выбран с помощью алгоритма BMC, либо мастер в сети единственный. На картинке показана процедура общения главного устройства и синхронизируемого.
![](https://habrastorage.org/storage2/97e/3ca/38a/97e3ca38a67fddde1751e7c4750012b5.png)
- Всё начинается с того, что Master отправляет сообщение Sync и одновременно записывает время отправки t1. Существует одно- и двухэтапные режимы работы. Отличить их очень легко: если присутствует сообщение FollowUp – то мы имеем дело с двухэтапной реализацией, пунктирной стрелкой показаны необязательные сообщения
- FollowUp сообщение отправляется вслед за Sync и содержит время t1. Если осуществляется передача в один этап, то Sync содержит t1 в теле сообщения. В любом случае t1 будет получено нашим устройством. В момент получения сообщения Sync на Slave генерируется таймпштамп t2. Таким образом мы получаем t1, t2
- Slave генерирует сообщение DelayReq одновременно с генерацией t3
- Master получает DelayReq сообщение, одновременно генерируя t4
- t4 отправляется Salve устройству в DelayResp сообщении
![](https://habrastorage.org/storage2/5a0/db8/819/5a0db88199aaf0adc8dafb1a58eb84a7.png)
Сообщения в сети
С помощью такого сеанса обмена, который показан выше, можно добиться успеха только в случае, если кварц генерирует идеально одинаковые частоты для синхронизируемых устройств. На деле же получается что частота часов разная, т.е. на одном устройстве за 1 секунду значение часов увеличится на 1 секунду, а на другом, например, на 1.000001 секунду. Отсюда появляется расхождение часов.
В стандарте описан пример вычисления отношения времени, прошедшего на Master и на Slave за определенный интервал. Это отношение будет являться коэффициентом для частоты Slave устройства. Но при этом есть указание, что подстройка может осуществляться различными способами. Рассмотрим два из них:
- Изменить тактовую частоту Slave устройства (пример в стандарте)
- Не менять тактовую частоту, но за каждый такт длительностью T значение часов будет увеличиваться не на T, а на T+∆t (используется в моей реализации)
В обоих способах потребуется вычислить разницу в значениях времени на Master устройстве за определенный интервал, а также разницу во времени, за этот же интервал на Slave устройстве. Коэффициент в первом способе:
![](https://habrastorage.org/storage2/c09/9ee/11c/c099ee11c06774e0327361968122a070.png)
Для второго способа требуется вычисление ∆t. ∆t – величина, которая будет складываться со значением времени каждый определенный интервал. На рисунке можно заметить, что в то время как на мастере прошло 22 – 15 = 7 секунд, на Slave прошло 75+(87-75)/2 –(30+ (37-30)/2) = 47.5
![](https://habrastorage.org/storage2/d2d/745/539/d2d7455397581e306287fbdb4ef3d167.png)
Частота – частота процессора, например, 25МГц — цикл процессора длится 1/(25*106) = 40нс.
В зависимости от возможностей устройства выбирается наиболее подходящий способ.
Чтобы перейти к следующему разделу, выразим смещение немного по-другому:
![](https://habrastorage.org/storage2/7f3/7da/52a/7f37da52a0297f49c9adf48b7059fe5a.png)
Режимы работы PTP
Заглянув в стандарт, можно обнаружить не единственный способ вычисления времени доставки. Существуют 2 режима работы PTPv2. Это E2E (End-to-End), он был рассмотрен выше, также описан режим P2P (Peer-to-Peer). Давайте разберемся, где какой способ применять и в чем их различие.
В принципе можно использовать любой из режимов по желанию, но их нельзя совмещать в одной сети.
- В режиме E2E время доставки вычисляется по сообщениям, пришедшим через множество устройств, каждый из которых проставляет в поле коррекции сообщения Sync либо FollowUP (если двухэтапная передача) время, на которое пакет задержался на этом устройстве (если устройства подключены напрямую, коррекция не проставляется, поэтому их детально рассматривать не будем). Используются сообщения: Sync/FollowUp, DelayReq/DelayResp
- В режиме P2P в поле коррекции заносится не только время, на которое задержался пакет, к нему прибавляется (t2-t1) (можно почитать в стандарте). Используются сообщения Sync/FollowUp, PDelayReq/PDelayResp/PDelayRespFollowUp
Согласно стандарту, часы, сквозь которые PTP сообщения проходят с изменением поля коррекции, называются Transparent Clock (TC). Посмотрим на рисунках, как передаются сообщения в этих двух режимах. Синими стрелочками указаны сообщения Sync и FollowUp.
![](https://habrastorage.org/storage2/4af/b3e/62b/4afb3e62bbf674d281bfac0e7b3e24fd.png)
Режим End-to-End
![](https://habrastorage.org/storage2/929/dec/d37/929decd374dcd811b2943082d3d8c99b.png)
Режим Peer-to-Peer
Видим, что в P2P режиме появились какие-то красные стрелочки. Это оставшиеся сообщения, которые мы не рассмотрели, а именно PDelayReq, PDelayResp и PDelayFollowUp. Вот сеанс обмена этими сообщениями:
![](https://habrastorage.org/storage2/aeb/370/32b/aeb37032b36a30a466809996f848e75c.png)
Ошибка времени доставки
Стандарт описывает реализацию протокола в различных типах сетей. Я использовал Ethernet сеть, и получал сообщения на Ethernet уровне. В таких сетях время доставки пакета постоянно меняется (особенно заметно, когда работаешь с наносекундной точностью). Для того чтобы отфильтровать эти значения применяются различные фильтры.
Что требуется фильтровать:
- Время доставки
- ∆t
- Смещение
В моем драйвере используется примерно такая же система фильтрации, как и в Linux демоне PTPd, исходники которого можно найти тут еще есть немного информации тут. Приведу лишь схему:
![](https://habrastorage.org/storage2/2b7/94f/e1c/2b794fe1c5f367f29a63b2982c42b771.png)
LP IIR (Infinite Impulse Response low-pass) фильтр (Фильтр с бесконечной импульсной характеристикой), описываемый формулой:
![](https://habrastorage.org/storage2/2e6/f34/5a2/2e6f345a2d840e42d441494f7f5541d0.png)
, где s – коэффициент, позволяющий регулировать срез фильтра.
Вычисление подстройки
Перейдём к подстройке, к той дельте, которая должна будет добавляться к значению секунды. Схема вычисления, используемая в моей системе:
![](https://habrastorage.org/storage2/999/06c/9f9/99906c9f900b270b1f025cab1fceb89f.png)
Я использовал фильтр Калмана, чтобы отфильтровать сильное дрожание подстройки из-за помех в сети, уж больно понравилась мне вот эта статья. А вообще, можно использовать любой фильтр, который нравится, главное чтобы сглаживал график. В PTPd, например, фильтрация попроще — вычисляется среднее от текущего и предыдущего значения. На графике можно посмотреть результаты работы фильтра Калмана в моем драйвере (показана ошибка подстройки, выражена в субнаносекундах на 25МГц чипе):
![](https://habrastorage.org/storage2/9d5/b21/5f8/9d5b215f84ca8d699e28920241004ece.png)
Переходим к регулированию подстройки, подстройка должна стремиться к константе, используется ПИ-регулятор. В PTPd регулируется смещения часов (настройка идёт по смещению), но я использую его для регулирования подстройки (особенность KSZ8463MLI). Видим, что контроллер настроен не идеально, но в моем случае такая регулировка достаточна:
![](https://habrastorage.org/storage2/f13/261/c1c/f13261c1cda1f56024b37bd5bf103193.png)
Результат работы
![](https://habrastorage.org/storage2/480/b71/5f4/480b715f4cc68572613075cdf3657c9b.png)
Результат показан на графике. Смещение часов в пределах -50нс до 50нс. Следовательно, я добился той точности, о которой говорится в многочисленных статьях. Конечно, множество мелких особенностей реализации осталось за кадром, но необходимый минимум был продемонстрирован.