Фрейминг для очень коротких последовательных пакетов

Мы разработали простой протокол фиксированной длины для встроенного устройства. Каждый пакет состоит всего из двух байтов:

 bits |  15..12  |    11..4     |  3..0   |
      |  OpCode  |     DATA     |  CRC4   |

Мы используем «кадрирование на основе crc», т.е. получатель собирает два байта, вычисляет CRC4 и, если он соответствует кадру, считается действительным. Как видите, нет ни начала, ни конца кадра.

Есть одна загвоздка: рекомендуемая длина сообщения для CRC4 составляет 11 бит, а здесь она вычисляется для 12 бит. Насколько я понимаю, это означает, что свойства обнаружения ошибок CRC ухудшаются (но я не уверен, насколько).

(Кстати, если кому-то нужен код для CRC4 (или любого другого) и он не чувствует себя достаточно опытным, чтобы написать его сам, у boost есть очень хорошая функция boost :: crc, которая может вычислить любой crc)

Проблема в том, что это кадрирование на основе crc не работает, и мы получаем ошибки кадрирования, то есть второй байт из одного сообщения и первый байт из следующего сообщения иногда формируют правильное сообщение.

У меня вопрос - есть ли способ исправить кадрирование без добавления байтов? Мы проводим довольно много времени, сжимая все в этих двух байтах, и было бы грустно просто выбросить их вот так. Однако у нас есть запасной бит в поле кода операции.

  • Фрейминг по времени будет не очень надежным, потому что наш радиоканал любит "плевать" сразу несколько пакетов.
  • Может быть, есть какой-нибудь другой метод обнаружения ошибок, который будет работать лучше, чем CRC4?

Если нам нужно добавить больше байтов, как лучше всего это сделать?

  • Мы можем использовать байт начала кадра и заполнение байтов (например, COBS) (+2 байта, но я не уверен, что делать с поврежденными сообщениями)
  • Мы можем использовать полубайт начала кадра и расширить CRC до CRC8 (+1 байт)
  • Что-то другое?

person Amomum    schedule 18.08.2016    source источник
comment
Как происходит обмен битами между процессором и устройством?   -  person 4386427    schedule 18.08.2016
comment
@ 4386427 процессор подключен к радиопередатчику по UART; радиоприемник подключается к устройству по UART.   -  person Amomum    schedule 18.08.2016
comment
Есть ли постоянный поток пакетов?   -  person 4386427    schedule 18.08.2016
comment
@ 4386427 более-менее. Имеется постоянный поток 4 пакета в секунду, но между ними также могут быть спорадические пакеты.   -  person Amomum    schedule 18.08.2016
comment
В этом случае обычно кадрирование пакетов выполняется по нескольким пакетам, т.е. вам нужно увидеть N последовательных пакетов с хорошим CRC, прежде чем вы начнете принимать пакеты. Не полагайтесь только на два байта для кадрирования. Это было бы слишком подвержено ошибкам.   -  person 4386427    schedule 18.08.2016
comment
@ 4386427, что увеличивает время реакции устройства, что очень нежелательно.   -  person Amomum    schedule 18.08.2016
comment
Да, но это необходимо, если вы хотите надежное обрамление. Вам понадобится период обучения при запуске, чтобы найти правильное обрамление. Например, вы можете сбросить первые 8 пакетов сразу после запуска, чтобы перейти в стабильное состояние КАДРА. После этого вы получите все пакеты (до тех пор, пока не возникнет ошибка, и вам нужно будет выполнить повторный фрейм). Это очень распространенный метод и обычно не является реальной проблемой, поскольку он возникает только при запуске (и снова после битовых ошибок на носителе). Например, проверьте протоколы PCM и ATM (разграничение ячеек).   -  person 4386427    schedule 18.08.2016
comment
Почему вы вообще для этого используете UART? Это такая старая и дрянная технология. Можно ли использовать SPI? Кроме того, поскольку вы упоминаете радио, CRC4, скорее всего, слишком мал, чтобы иметь какое-либо применение - радиопомехи будут давать практически 100% случайные данные во всем пакете. Если радио не имеет встроенного механизма контрольной суммы, я бы использовал как минимум CRC16.   -  person Lundin    schedule 18.08.2016
comment
@Lundin UART и SPI в основном одинаковы. Ну, SPI синхронный, ну и что? Это не делает его менее подверженным ошибкам. И это все равно не вариант. Радиомодули имеют собственные контрольные суммы, поэтому наша CRC в основном предназначена для кадровой синхронизации и паранойи.   -  person Amomum    schedule 18.08.2016
comment
@ 4386427 спасибо, мы рассмотрим эту идею!   -  person Amomum    schedule 18.08.2016
comment
@Amomum Разница в том, что SPI синхронизируется на каждом бите, а UART синхронизируется только один раз на 10 бит. Когда данные синхронизируются по каждому биту, всегда есть возможность исследовать длину в битах для обнаружения шума - метод, который намного превосходит CRC. Очень маловероятно, что шум имеет точную длину одного бита.   -  person Lundin    schedule 18.08.2016
comment
@Lundin 1) Это на самом деле зависит от тактовой частоты 2) На самом деле у нас нет проблем с ошибками, наша проблема - кадровая синхронизация, и SPI не поможет с этим 3) Я не уверен, как можно измерить длину в битах, если SPI для связи используется периферийное устройство. 4) Если в проводах данных появляются ошибки, часы SPI также могут быть повреждены и битовая синхронизация будет потеряна. Я что-то упускаю?   -  person Amomum    schedule 18.08.2016
comment
@Amomum Зависит от того, что делает радиочип. Если он всегда преобразует последовательные данные в какой-то специализированный фрейм протокола, то способ последовательной шины не имеет значения. Но если радиочип тупой и просто ретранслирует переданные ему двоичные данные, то UART совершенно не подходит. Вам может понадобиться манчестерское кодирование или аналогичный надежный формат данных, который можно генерировать с помощью SPI, но не с помощью UART.   -  person Lundin    schedule 18.08.2016
comment
@ Лундин, извини, но я не совсем понимаю, что ты имеешь в виду. В любом случае, я действительно не знаю, что на самом деле делает радиочип (я предполагаю, что он оборачивает наши пакеты в некоторые свои собственные кадры и вычисляет какую-то контрольную сумму, но мы иногда получаем фрагменты пакетов), я не могу это изменить или изменить это интерфейс для SPI.   -  person Amomum    schedule 18.08.2016
comment
Маленькая идея: обменяйте 1 бит / байт с помощью 2-битной CRC и используйте эти 2 бита для кадрирования каждого из 2 байтов. Или, кроме того, отправляйте данные с использованием нечетной четности вместо отсутствия четности (на 10% медленнее) - это в сочетании с 2-битным CRC эффективно возвращает к 4-битному CRC.   -  person chux - Reinstate Monica    schedule 18.08.2016
comment
zlib.net/crc_v3.txt   -  person too honest for this site    schedule 19.08.2016
comment
@chux: Я думаю, что все это довольно широко распространено и слишком широко.   -  person too honest for this site    schedule 19.08.2016


Ответы (3)


Обычный способ сделать то, что вы просите, - это "поиск кадра" при запуске и требование N последовательных хороших пакетов перед тем, как принять какие-либо пакеты. Это может быть реализовано с помощью конечного автомата с 3 состояниями: HUNT, LOF (потеря кадра), SYNC.

Это могло быть что-то вроде:

#define GOOD_PACKETS_REQUIRED_BEFORE_SYNC 8
int state = HUNT;
int good_count = 0;

Packet GetPacket(void)
{
    unsigned char fb = 0;
    unsigned char sb = 0;

    while (1)
    {
        if (state == HUNT)
        {
            fb = sb;
            sb = GetNextByteFromUART();

            if (IsValidCRC(fb, sb))
            {
                state = LOF;
                good_count = 1;
            }
        }
        else if (state == LOF)
        {
            fb = GetNextByteFromUART();
            sb = GetNextByteFromUART();

            if (IsValidCRC(fb, sb))
            {
                good_count++;
                if (good_count >= GOOD_PACKETS_REQUIRED_BEFORE_SYNC)
                {
                    state = SYNC;
                }
            }
            else
            {
                state = HUNT;
                good_count = 0;
            }
        }
        else if (state == SYNC)
        {
            fb = GetNextByteFromUART();
            sb = GetNextByteFromUART();

            if (IsValidCRC(fb, sb))
            {
                return packet(fb, sb);;
            }

            // SYNC lost! Start a new hunt for correct framing
            state = HUNT;
            good_count = 0;
        }
    } 
}

Вы можете найти несколько стандартных протоколов связи, которые используют этот (или аналогичный) метод, например Банкомат и E1 (https://en.wikipedia.org/wiki/E-carrier ). Есть разные варианты принципа. Например, вы можете захотеть перейти от SYNC к LOF при получении первого плохого пакета (с уменьшением good_count), а затем перейти от LOF к HUNT во втором последовательном плохом пакете. Это сократит время, необходимое для изменения кадра. Выше показан очень простой вариант.

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

Нужен ли вам CRC или можно использовать фиксированное слово кадра (например, 0xB), зависит от вашего носителя.

person 4386427    schedule 18.08.2016
comment
Packet GetPacket() неверно или вы забыли вернуть значение. Пожалуйста, используйте правильные прототипы-деклараторы. Собственно компилятор должен предупредить. - person too honest for this site; 19.08.2016
comment
@Olaf - это не полная функция (это была бы ужасная реализация ...) - это просто показать какой-то принцип. Однако, если все структуры и (отсутствующие) функции были реализованы, я не понимаю, почему прототип должен быть неправильным. - person 4386427; 19.08.2016
comment
Для начала, потому что он возвращает int. Во-вторых, он использует декларатор в старом стиле, который является устаревшей функцией и настоятельно не рекомендуется. Вы можете прочитать стандарт. - person too honest for this site; 19.08.2016
comment
@Olaf - почему вы думаете, что он возвращает int? - person 4386427; 19.08.2016
comment
Я так не думаю. Но компилятор C ожидает этого. Вы должны прочитать стандарт, что означает отсутствие спецификатора типа в объявлении функции (и что его не следует использовать примерно с 27 лет). - person too honest for this site; 19.08.2016

Есть одна загвоздка: рекомендуемая длина сообщения для CRC4 составляет 11 бит, а здесь она вычисляется для 12 бит.

Нет, здесь он рассчитан на 16 бит.

Насколько я понимаю, это означает, что свойства обнаружения ошибок CRC ухудшаются (но я не уверен, насколько).

Рекомендации по CRC, вероятно, относятся к тому, есть ли у вас 100% шанс найти однобитную ошибку или нет. Все CRC борются с многобитовыми ошибками и не обязательно их обнаруживают.

При расчетах надежности CRC UART вы также должны учитывать стартовый и стоповый биты. Здесь также могут возникнуть битовые ошибки, и в этом случае оборудование может помочь или не помочь в обнаружении ошибки.

второй байт из одного сообщения и первый байт из следующего сообщения иногда образуют правильное сообщение

Конечно. У вас нет механизма синхронизации, чего вы ожидаете? Это не имеет ничего общего с CRC.

У меня вопрос - есть ли способ исправить кадрирование без добавления байтов?

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

Выбор метода зависит от характера данных и вашей спецификации. Никто в SO не может сказать вам, как выглядит ваша спецификация.

Может быть, есть какой-нибудь другой метод обнаружения ошибок, который будет работать лучше, чем CRC4?

Скорее всего, не. CRC - практически единственный профессиональный алгоритм контрольной суммы. Полиномы выбираются на основе исключительной природы шума - они выбирают полином, который как можно меньше напоминает шум. Однако это в основном представляет академический интерес, поскольку ни один гуру CRC не может знать, как выглядит шум в вашем конкретном приложении.

Альтернативы - это суммы, xor, четность, количество единиц и т.д., все они довольно плохи с точки зрения вероятности.

Если нам нужно добавить больше байтов, как лучше всего это сделать?

Никто не может ответить на этот вопрос, не зная природы данных.

person Lundin    schedule 18.08.2016
comment
Просто хотел добавить комментарий к полиномиальной части: некоторые полиномы лучше справляются с пакетными ошибками, чем случайные ошибки и т. Д. Итак, есть некоторые шумовые характеристики, к которым вы можете адаптировать свой полином crc. В этой публикации CMU говорится о полиномиальном выборе во встроенном контексте: repository.cmu. edu / cgi / - person Morten Jensen; 18.08.2016
comment
›› Нет, здесь он рассчитан на 16 бит. Нет, это 12 бит данных и 4 бита CRC. Я использую boost :: crc_basic и вычисляю по крупицам. ›› У вас нет механизма синхронизации, чего вы ждете? Насколько я знаю (но я не эксперт) кадровая синхронизация на основе crc - это механизм синхронизации, и он используется .. en.wikipedia.org/wiki/CRC-based_framing ›› Никто не может ответить на этот вопрос, не зная природы данных. байт данных может быть любым произвольным байтом. - person Amomum; 18.08.2016
comment
@Amomum На стороне получателя CRC обычно вычисляется как на основе данных, так и на FCS. То есть вы выполняете XOR всего пакета против многочлена и проверяете, равен ли результат 0 или нет. - person Lundin; 18.08.2016
comment
@Lundin отличается ли это от вычисления CRC только для данных и сравнения его с FCS в пакете? - person Amomum; 18.08.2016
comment
@Lundin: биты кадрирования не являются частью данных и не являются константой. Не уверен, что они добавляют к вычислению CRC (а это не для типичных протоколов). И результат равен нулю только в том случае, если CRC не инвертирован (что рекомендуется). И это тоже не просто XOR. - person too honest for this site; 19.08.2016
comment
@Amomum Разница в том, что вы должны рассматривать FCS как часть данных. - person Lundin; 21.08.2016
comment
@Lundin, это очевидно. Я имел в виду, есть ли практическая разница? Какие преимущества? - person Amomum; 21.08.2016
comment
@Olaf Традиционно CRC рассчитывалась как побитовое исключающее ИЛИ, и это можно сделать даже с помощью чистой цифровой электроники. Вы также можете вычислить его с помощью побитового XOR в программном обеспечении, но это, как правило, довольно неэффективно. Инвертирование регистра до или после вычисления очень зависит от CRC. Некоторые делают, некоторые нет. Почему лучше или хуже - не очевидно ... Я сомневаюсь, что за этим есть какое-то обоснование, просто существуют другие стандарты (или отсутствие стандарта). - person Lundin; 21.08.2016
comment
@Lundin: Я хорошо знаю математику, лежащую в основе CRC, и то, как они вычисляются в аппаратном обеспечении, а также в программном обеспечении. К вашему сведению: причина инвертирования CRC - обнаружение завершающих 0-битов после CRC. См., Например, Ethernet. Конец кадра определяется внеполосной сигнализацией, поэтому до конца не ясно, когда был получен CRC. Таким образом, если EOF-сигнал обрабатывается с PHY, а аккумулятор CRC соответствует определенному магическому коду (он не равен нулю из-за инверсии), передача была успешной. В сети есть неплохие уроки по этому поводу. - person too honest for this site; 22.08.2016

Если CRC предназначен в основном для паранойи (из комментариев), вы можете отказаться от некоторой надежности проверки ошибок и времени процессора для кадрирования.

Поскольку в коде операции есть свободный бит, всегда устанавливайте самый старший бит первого байта равным нулю. Затем перед передачей, но после вычисления CRC, установите самый старший бит второго байта в единицу.

Таким образом, кадр представляет собой два последовательных байта, где первый старший бит равен нулю, а второй - единице. Если два байта не прошли проверку CRC, установите самый старший бит второго байта в ноль и пересчитайте, чтобы увидеть, был ли бит пакета перевернут перед передачей.

Обратной стороной является то, что CRC будет вычисляться дважды примерно в половине случаев. Кроме того, установка бита для кадрирования может привести к тому, что недопустимые данные будут соответствовать CRC.

person D Krueger    schedule 19.08.2016