РЕШЕНО Modbus без парсинга и ошибок.

Тема в разделе "Глядите, что я сделал", создана пользователем Алопеций Шнифт, 4 мар 2026.

  1. Итак, сегодня будем принимать Модбас без парсинга. Нет, правда, принимать будем, а проверять даже номер слейва не будем.
    Ниже пример сверхминималистичного Modbus. Накрывает (с оговорками) практически все функции с восьмибайтовыми запросами для Слейва.
    Да, этой большей частью трюкачество, программистский кунштюк.
    Но оно действительно работает (причём сверхэффективно и безошибочно), так что этот трюк вполне может быть полезен.
    Оговорки:
    • - без модификации алгоритма будет отвечать только на восьмибайтовые запросы. Что накрывает полностью широчайший класс слейвов - более или менее интеллектуальные датчики.
    Более конкретно, отрабатываются (со стороны слейва) следующие функции:
    0x01 - Read Coils
    0x02 - Read Discrete Inputs
    0x03 - Read Holding Registers
    0x04 - Read Input Registers
    А в дальнейшем добавим обработку ещё и
    0x05 - Write Single Coil
    0x06 - Write Single Register
    0x08 - Diagnostics (большинство подфункций),
    что сделает наше устройство полнофункциональным прибором, способным решать широкий класс задач.

    • отсутствуют сообщения об ошибках - да, нехорошо, но по большому счёту эти сообщения никак не помогают Мастеру. Отказ - в любом случае отказ.
    • в предложенном алгоритме Слейв начнёт отвечать практически мгновенно. Это не совсем по стандарту, хотя, конечно, можно вставить и задержку.

    Итак, собственно код:
    Код (C):

    #define PRESSURE_KEY    0x5AA5000A1C2D0405  //Ключ - полная копия ожидаемого запроса мастера.

    //Эта функция вызывается там, где байт материализуется из линии.
    //У меня это в isr
    int mbNewByte(uint8_t byte)
    {
        static volatile uint64_t shiftBuf;
        shiftBuf = (ShiftBuf >> 8) | byte;
        if(shiftBuf == PRESSURE_KEY)
        {
            //Всё, разборка пакета успешно заверщена, можно отвечать.
            //Разумеется, актуальный ответ должен быть готов всегда в виде, пригодном для отправки.
            return 1; //Пакет принят!
        }
        return 0; //Процесс приёма не заверщён.
    }
     
    Вы заметили? Мы безошибочно приняли запрос, но не парсили даже номер слейва. Но неявно-то он присутствовал и участие в сравнении принимал, скажете Вы.
    Да, согласен. Но, во-первых, это отнюдь не классический парсинг (у нашего варианта отсутствует простор для ошибок - всё предельно жёстко), а во-вторых, дальше (если будет время и возникнет интерес) выложу и вариант, где номер абонента вообще никак не участвует.
    Для первого раза, мне кажется, вполне хватит. Что у нас получилось? У нас получился Modbus для датчика. Принимать от Мастера он пока ничео не умеет, как и принимать конфигурацию (добавится, как и обещал, позже).
    Но зато расходы - 8 байт ОЗУ + 8 байтов флэш, не считая кода (но его совсем немного), без всяких дополнительных переменных и состояний, не к ночи будь помянуты.

    Примечательные особенности этого кода.
    • - очень высокое быстродействие в сравнении с парсинговыми алгоритмами, но это не главное
    • - главное - гарантированная безошибочность. То есть если требуемые байты в нужном порядке появятся в линии, они будут неминоемо распознаны с неумолимой стремительностью домкрата ибо
    • - алгоритм обладает темпоральной инвариантностью - расстояние между байтами может либо вообще отсутствовать, либо исчисляться годами.
    • - эффектный трюк с uint64_t в общем-то совершенно необязателен. uint8_t [8] справится ничуть не хуже.
    Краткое объяснение: Слейв всегда знает, с какими запросами может обратиться к нему Мастер. И на другие ответить просто не может. Поэтому запросы мастера и принимаются в качестве ключа.
    То есть, сколько запросов от мастера собираетесь обрабатывать, столько ключей и потребуется.

    Кстати, аналогичный "паттерновый" подход вполне реализуем и для мастера. Но там будет другой подход (если руки дойдут): вычислительная эффективность будет сознательно принесена в жертву простоте и однообразности, т.е. в конечном итоге всё той же безошибочности.

    Кстати 2: Modbus - не самый удобный протокол для этого алгоритма. IEC 60870-5-101, например, будет принять ещё проще.

    Кстати 3: Общий объём данных можно сократить ещё на 6 байт флэш. Толку в этом никакого, просто из любви к искусству.
     
    Последнее редактирование: 4 мар 2026
  2. parovoZZ

    parovoZZ Гуру

    Так это вовсе не модбас. А кастомный API частично совместимый с модбас.
     
  3. Вовсе нет. Мастер никак не сможет отличить такого слейва от 'традиционного'.
    Точнее, всё же сможет, но для этого нужно преднамеренно нарушать стандарт. Но только отличить, предъявить какое-либо нарушение стандарта будет всё равно нельзя. По причине его отсутствия.
    Ну да, неполный. А Вы часто видели полный, если не брать библиотеку целиком?
    Но то, что реализовано - самый натуральный Модбас.
     
  4. parovoZZ

    parovoZZ Гуру

    Мастер может запрашивать любое количество регистров и с любого адреса в доступном диапазоне. Данная реализация это не позволит сделать никак. К тому же в модбас обязателен ответ с ошибкой, если мастер запрашивает не так. Здесь этого тоже нет.
     
  5. То есть? Слейв обязан отвечать на любой адрес регистра?
    И должен, соответственно, информационно наполнить более чем на 300кб регистровых значений?
    Да, должен возвращать ошибку, если обнаружил свой адрес в корректном с точки зрения стандарта пакете, а не 'если мастер запрашивает не так', кстати . Чего не делает, о чём я честно и предупредил.
    Да, сообщения об ошибках мало кто вообще принимает и анализирует, а уж процессу обмена они не помогают вообще никак (максимум мастер может исключить такого слейва из опроса навечно), но тем не менее.

    И я хочу ещё раз подчеркнуть, что эффективность (и даже эффектность), скорость и простота - отнюдь не самое важное в этом алгоритме.
    Тут рассматривается совершенно иной (абстрактный или паттерновый) способ приёма фреймов, в противовес (не для глобальной замены!) классическому парсингу.
    Ну а легковесность просто позволит разместить подобный модбас в совершенно крошечных по памяти и совершенно несерьёзных по производительности устройствах. А крайняя простота и вытекающая из неё безошибочность - да, важно, но всё же не главное.
     
  6. parovoZZ

    parovoZZ Гуру

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

    я работаю в промышленности и ситуации бывают разные. Ошибка 8 очень полезна при первой диагностике. В работе же самые ходовые 15 и 6 ошибки.

    не совсем понятно, что такое классический парсинг? Мы приняли первый байт. Это адрес. Он совпадает с нашим? Если да, то принимаем и анализируем следующий байт (это номер функции) и так далее, пока не примем весь пакет (сверять CRC пакета очень даже обязательно!). Анализировать можно по-разному - по счётчику принятых байт; зеркалировать на структуру, а затем по значениям полей принимать дальнейшие решения. Но это самое простое. Самое тяжёлое в модбас - считать CRC.
    Пауза в модбасе придумана для того, чтобы один слейв не принимал пакет от другого слейва в ожидании его окончания. Достаточно дождаться конца активности на линии, включить таймер и по окончании этого таймера самый первый байт будет гарантированно будет адресом слейва.
    Так как ничего этого нет в предложенном варианте. то я и пишу - кастомный API на базе модбас. Но с настоящим модбас он не совместим.

    Зачем? Достаточно в виде массива выделить в памяти только то, что мы хотим отдать мастеру. На все остальные адреса (а так же если запрошенный диапазон вылезает из разрешённого) отдаём ошибку 8.
    Выделять буфер необходимо, если мы хотим что-то писать в себя по 6 функции. Сразу писать мы не можем - надо принять пакет, где-то его разместить и посчитаnь его CRC.
     
  7. Да, это один из вариантов парсинга. Из многих. И нигде, заметьте, в документации не написано, что он должен быть именно таким и никаким другим. Так что в данном случае Ваше второе предложение никак не вытекает из первого.
    По поводу паузы в 3.5 и 1.5 - она была абсолютно ненужной и ничего не улучшающей ещё тогда, в конце 70-х. Но это потом, сейчас рубиться на эту второстепенную тему не готов.

    Есть такой способ - просто указать в сопроводительной документации, что информация отдаётся только одним куском и только с одного адреса.
    Слейв тоже может распоряжаться имеющейся у него информацией как вздумается. Не говоря уже о том, что одним куском - это и более эффективно, чем по медленным линиям собирать что-то. И уже потом объединять со скоростью микропроцессора.

    На один слейв? Я чего-то не понял, очевидно.

    Да там же и хранить, в восьмибайтной переменной. Я хотел это попозже выложить, но раз повод возник, то сейчас.

    Сначала необходимые пояснения. Изначально я рассчитывал на работоспособность алгоритма на самых слабых МК. И по памяти, и по производительности. А что? Маленькие тоже могут хотеть в Модбас!
    Насколько мне известно, самый маленький контроллер, который всё ещё можно зачем-то купить (это какой-то из младших Эттайни) имеет ОЗУ в 32 байта. И это отнюдь не приговор. Вот из этих размеров и станем постепенно исходить.
    Но вычислительной мощностью тоже не следует разбрасываться.
    Я вот про что: мы можем заранее просчитать crc для первых 4-х байт, а потом, после прихода всего пакета, досчитать crc всего для двух байтов. Классика: сэкономим производительность, но потеряем 2 байта. На фоне того, что их всего 32, это овердофига. Но 8 бит - это тоже 8 бит, и 16-20 МГц - совсем немного..
    Итак, строчки с "//*" это быстрый вариант. В исходном виде - медленный.

    Код (C):

    uint16_t updcrc(uint16_t oldcrc,uint8_t newbyte);
    uint16_t crc16(uint8_t *buf,uint8_t sz);
    //*предрасчитанный crc для первых четырёх байт. Считается одновременно  с ключом
    //*uint16_t xcrc = 0x55AA; //значение исключительно для примера

    #define PRESSURE_KEY    0x00100601    //Ключ, как видите, сокращён до щшапки запроса.

    //Эта функция вызывается там, где байт материализуется из линии.
    //У меня это в isr
    int mbNewByte(uint8_t byte)
    {
        static volatile uint64_t shiftBuf;
        uint16_t  crc;
        shiftBuf = (ShiftBuf >> 8) | byte;
        if(((uint32_t *)(&shiftBuf))[1] == PRESSURE_KEY)    //Да, такой каст красотою не блещет, согласен. Но работает.
        {
    //*        crc = updcrc(crc, ((uint8_t *)&shiftBuf[4]))    //... и такой тоже
    //*        crc = updcrc(crc, ((uint8_t *)&shiftBuf[5]))    //
            crc = crc16(shiftBuf,6);    //это медленный вариант, закомментировать при переходе к быстрому
            if(crc == (uint16_t)shiftBuf)
            {
                //Всё, разборка пакета успешно заверщена, можно отвечать.
                //Разумеется, актуальный ответ должен быть готов всегда в виде, пригодном для отправки.
                return 1; //Пакет принят!
            }
        }
        return 0; //Процесс приёма не заверщён.

     
    Красоты стало поменьше, эффективность пострадала незначительно.
    Зато наш Модбас научился принимать конфигурацию и передавать воздействие на объект.
    Замечания по ходу: я бы crc вообще не считал, ибо вероятность случайного появления ключа в линии многократно (тысяч так в 15 раз) меньше, чем нераспознавание ошибки средствами crc (разумеется, при абсолютно случайном потоке байтов). Но понимаю, что многие не поддержат, поэтому crc считаем. Хотя это всё равно что заматывать танк в фольгу для лучшей бронезащиты.
    А, ну и ключ там у меня слишком простой, но это для наглядности. В продакшене его нужно сделать максимально помехозащищённым.
     
    Последнее редактирование: 5 мар 2026
  8. parovoZZ

    parovoZZ Гуру

    Я привел то, что лежит на поверхности на утверждение, что разбор пакета модбас сложный. Как видим, ничего сложного. Приведенная же автором реализация гораздо сложнее по вычислительным затратам.
    Мастеру она и не нужна. А вот слейву да. Так как если отвечает соседний слейв потоком байтов, то отличить payload от первого байта с адресом в принципе невозможно. Принимать в себя все допустимые 125 регистров ... можно, но зачем? Дожидаемся паузы и следующий байт гарантированно будет адресом. К тому же нельзя забывать, что линии имеют такую особенность, как помехи.
    Я встречал такое всего один раз. Но, на удивление, не нашел отступлений от стандарта.
    Слейв - это сервер и он просто обязан отдать именно то, что у него запрашивают. так требует стандарт.
    Не всегда. Надо в паузах читать что-то другое, а принимать все 125 регистров, из которых сменился всего один -расточительно. А какой изменился, лежит в другом регистре. Я вот только что сдал такой проект. Суммарно там более 10 000 регистров читать их все последовательно - это ооочень долго.
    Если слейв допускает запись в большое количество регистров, то таким же размером надо держать временный буфер. Мы понятия не имеем, как мастеру взбредет записывать регистры : по одному, пакетом с начала, с середины. Да как угодно - в стандарте никаких ограничений нет.
     
  9. Во-первых, стандарт требует паузы только от мастера. А алгоритм для слейва и этой паузы не требует никак.
    Во-вторых, по предложенному алгоритму слейвы работают без пауз вполне устойчиво. Ну без пауз - это в экспериментальном порядке и со своим мастером. Тем не менее, байты в линии идут внавал, и эффективная скорость достигает 950 б/c. Да, ответ начинается менее чем через микросекунду после получения последнего байта запроса.
    В гетерогенной сети, в продакшене, конечно, пауза выдерживается. Хотя моим слейвам она и не нужна.
    Если байты в требуемом порядке появятся в линии, алгоритм их примет. Вне зависимости от присутствия или наличия других слейвов и степени зловредности мастера. Это математика, ошибок быть не может.
    И, кстати, это легко опровергнуть. Просто предложив массив байт, содержащий внутри корректный блок в каком угодно обрамлении, каковой блок гарантированно не будет принят алгоритмом.
    Но я утверждаю, что такой массив просто не может существовать в природе.

    Если он может это сделать. В противном случае обязан ответить ошибкой.
    И, кстати, спасибо Вам - я пересмотрел своё отношение к ошибкам и осудил себя за эгоизм. Действительно, программисту сообщения об ошибках или совсем не нужны (как мне), или в незначительной степени. А вот наладчикам без них может быть очень неуютно.
    Так что я возьму таймаут и допилю ошибки во 2-й вариант (в 1-й, красивый, они никак не влезут, к сожалению). Тогда, если получится, то получится уже полноценный слейв без скидок.
    На остальное отвечу потом - уж больно интересно стало с ошибками разобраться..
     
  10. Но пока не углубился в увлекательный мир ошибок, исключений и хардфолтов, всё-таки отвечу сам себе.
    32-байта - это всё же чересчур. Забыл про ISR, генерацию ключа и хоть какую-нибудь полезную нагрузку. На спор сделаю и работать будет, но в продакшн под это не подпишусь. Даже 64 неуютно лично мне, хотя алгоритму достаточно 48 (но таких контроллеров нет).
    Вот со 128-ми уже можно начинать.
    Так и запишем: минимальные требования - 128 байт ОЗУ.
    А, ну и тогда окончательно останавливаемся на быстром варианте алгоритма. 2 байта из 128-ми - не так много.
     
    Последнее редактирование: 6 мар 2026
  11. Так, вот что получилось в результате:
    Код (C):

    #include <stdint.h>

    uint16_t updcrc(uint16_t oldcrc, uint8_t newbyte);
    uint16_t crc16(uint8_t* buf, uint8_t sz);
    void send_err_msg();
    uint16_t xcrc = 0x55AA; //значение исключительно для примера
    int getByte(uint8_t *byte);

    #define PRESSURE_KEY    0x00100601      //Ключ сокращён до заголовка пакета
    uint8_t volatile myAddr = 1;            //Теперь адрес слейва участвует в разборе
                                            //Хотя для чистоты можно было бы его вытащить из ключа


    //Эта функция вызывается там, где байт материализуется из линии.
    //В контроллере у меня это в isr
    int mbNewByte(uint8_t byte)
    {
        static volatile uint64_t shiftBuf;
        uint16_t  crc=0;
        shiftBuf = (shiftBuf >> 8) | byte;
        if (((uint32_t*)(&shiftBuf))[1] == myAddr)      //Убеждаемся, что принят потенциально наш пакет
        {                                               //Да, такой каст красотою не блещет, согласен. Но работает.
            crc = xcrc;                                     //Начинаем с предрасчитанного xcrc
            crc = updcrc(crc, ((uint8_t*)&shiftBuf)[4]);    //... и досчитываем два байта.
            crc = updcrc(crc, ((uint8_t*)&shiftBuf)[5]);
            if (crc == (uint16_t)shiftBuf)                  //Провнерка, что пришёл формально корректный пакет
            {
                if (shiftBuf == PRESSURE_KEY)   //Тут простор для сокращения пары-тройки тактов
                {
                    //Всё, разборка пакета успешно заверщена, можно отвечать.
                    //Разумеется, актуальный ответ должен быть готов всегда в виде, пригодном для отправки.
                    return 1; //Пакет успешно принят!
                }
                return 0;   //Теперь это сообщение об ошибке
            }
            return -1;      //приём пакета в процессе
        }
        else return -1; //приём пакета в процессе
    }


    int main()
    {
        uint8_t byte;
        for (;;)
        {
            if (getByte(&byte))
            {
                switch (mbNewByte(byte))
                {
                    case -1:    //приём не завершён, просто идём дальше
                        break;
                    case 0:     //принят формально корректный ошибочный блок
                        send_err_msg(); //посылаем сообщение об ошибке
                        break;
                    case 1:     //принят полностью корректный запрос мастера
                                //Тут отправляем ответ
                        break;
                }
            }
        }
    }
     
    Теперь наш модбас умеет в ошибки.
    Но инженерно-техническая красота убита полностью и окончательно. И не только.
    Я как раз придумал звучное, как мне казалось, название алгоритма - zeroParsing Modbus, как бац - адрес абонента уже участвует .в сравнении, сука такая!
    А это, как ни крути, всё же элемент парсинга.
    Поэтому название пришлось изменить на Nearly zeroParsing Modbus с известной аллюзией, а простое ZPM оставить для первого, 'красивого' варианта.
    А текущий будет, соответственно, NZPM. Чтобы не путаться.
    Эффективность, в отличие от красоты, почти не пострадала, а вес так и вовсе.не пострадал.
    Функции
    Код (C):

    uint16_t updcrc(uint16_t oldcrc, uint8_t newbyte);
    void send_err_msg();
    int getByte(uint8_t *byte);
     
    должны быть реализованы пользователем. Но без этого никак.
     
    Последнее редактирование: 6 мар 2026
  12. Согласен. Да, такие случаи бывают.
    Сам предпочитаю полный опрос выборочному - Модбас, на мой взгляд, не тот протокол, из которого нужно выжимать максимальную эффективность.
    Но вообще вопрос вкуса. Мне наиболее важно - надёжно, безошибочно, и максимально однообразно. Кому-то скорость и эффективность.

    ?
    Не было такого утверждения. И даже мыслей таких не было.
    Я (наверняка как и Вы) за свою жизнь реализовал за десяток разных модбасов различной степени навороченности, в разных средах, на разных языках и платформах. Но вот сложным этот немудрёный протокол мне ни разу не казался.

    А вот это утверждение требует как минимум обоснования. По моим замерам, ZPM в ~4 раза быстрее классика (~70 тактов против 18 на байт). Хотя, как уже говорил, скорость - не главное в этом алгоритме. Хоть она и присутствует в полный рост.
    Отредактировано - выше для stm32, это реальные цифры. Для восьмибиток будет 1.5 - 2 раза, но это расчётные цифры, живой восьмибитки под рукой нет.

    Это просто не понял. По-моему, мы просто говорим о разных вещах. Нет?
    Мой Модбас обрабатывает только 8-байтные запросы от мастера. Зато теперь все и с сигнализацией об ошибках.
     
    Последнее редактирование: 6 мар 2026
  13. parovoZZ

    parovoZZ Гуру

    вот скрин из стандарта
    upload_2026-3-6_17-49-50.png
    нигде про мастера не сказано ничего. 3.5 бита между кадрами касается всех. Оно и понятно - с точки зрения слушающих, мастером является тот, кто ведёт передачу.


    и, мало того, он отдаст ответ в линию не впопад. Т.е. вся связь при таком раскладе просто ляжет.


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

    И снова - данный подход - это modbus-based algorithm, но с модбас полной совместимости нет.
    Если слейв один на линии, то адрес можно вообще убрать. Если сделать четырёхпроводку (точка-точка RS-422 или более привычный 4W RS-485), то и понятие мастера исчезнет - оба устройства будут просто общаться.
     
  14. Не могли бы Вы пояснить, при каких условиях это произойдёт?

    В моей реализации слейв отвечает только при полном совпадении с ключом, который включает адрес, функцию, данные и CRC. Такой пакет не может быть «чужим» — он адресован именно мне.

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

    Если вы имеете в виду коллизию из-за того, что мастер не ожидает ответа, то это противоречит логике протокола: мастер всегда ждёт ответ после запроса.

    Буду благодарен за конкретный пример или сценарий, при котором связь действительно может "лечь" из-за моего алгоритма.

    В остальном, мне кажется, мы уже начали ходить по кругу. Если у кого-то из нас появятся новые доводы или конкретные примеры — с удовольствием продолжу. А пока предлагаю на этом остановиться.

    Спасибо за дискуссию — она помогла мне улучшить алгоритм (добавил обработку ошибок) и лучше аргументировать свою позицию.
     
  15. parovoZZ

    parovoZZ Гуру

    ключ:
    может оказаться в любой части кадра, который принимает или отдаёт соседний слейв. А так как предложенный алгоритм понятия не имеет, из какой части кадра выдран данный ключ (алгоритм никак не отслеживает паузы между кадрами), то он тупо ответит. Хорошо, если скорость передачи высокая и этот ответ придёт уже после ответа другого слейва и мастер его просто проигнорит. Но если нет, то мастер перезапросит у слейва ещё раз этот пакет и снова произойдёт всё тоже самое. В результате связь легла.
     
  16. Э? 8 байт подряд? Случайно? Просто совпадёт?
    Ну через 600 млн лет (при постоянной передаче на 9600) - да, такой случай точно произойдёт. Это в абсолютно случайном потоке. В детерминированном (ну допустим, что мастер со слейвами обменивается исключительно содержимым ключей), как ни странно, ещё позже.
     
  17. parovoZZ

    parovoZZ Гуру

    Очень даже легко. Например, когда передаешь упакованные битовые статусы. В результате мы имеем запрещенную комбинацию. В промышленной связи такое недопустимо в принципе.
     
  18. Расшифруйте, я не понял. Ни чем упакованные биты в смысле помехозащещённости отличаются от других видов информации, ни про запрещённую комбинацию, к которой почему-то именно эти биты должны приводить.
    В общем, алгоритм примет ключ несанкционированно только в том случае, если мастер или слейв впендюрят ключ в пакет целиком. Но это не мастер, это уже диверсант, целенаправленно пытающийся развалить сеть. А про вероятность случайного расположения ключа в пакете я уже писал.
     
  19. parovoZZ

    parovoZZ Гуру

    Это здесь при чем?

    Расшифровать что? Что каждый из 16 бит в четырех регистрах не зависим и может создать абсолютно любую комбинацию? Или что не понятно?

    Про это и речь. Но в модбасе нет такого понятия, как ключ. В модбасе есть понятия кадр, внутри которого может быть до 125 регистров. один регистр - это всегда два байта. В потоке, где запрашивается порядка 1000 регистров с различной информацией, могут складываться абсолютно любые комбинации. Сложиться надо всего двум регистрам.
    Но дело вообще не в этом. А в наличии запрещенной комбинации. Вероятность ее появления никого не интересует.
    Поэтому предложенный алгоритм ничего общего с модбас не имеет.
     
  20. Непонятно - и что из этого?

    Извините, это уже демагогия.
    В инженерии вероятность интересует всех. На вероятностях строятся:
    CRC (вероятность необнаружения ошибки)
    Таймауты (вероятность потери пакета)
    Резервирование (вероятность отказа)
    Метрология (помните Стьюдента? там всё на вероятности построено)
    Сопротивление материалов (где-то с 70-х гг прошлого века) и всё, что оттуда вытекает на практике.
    В программировании это выбор и характер буферов
    * * *

    ... но даже если это не так, следующее утверждение никак не вытекает из предыдущего:
    ... и всё-таки поясните про упакованные статусные биты - чем они так особо страшны? Я как-то был уверен, что один бит не отличается от любого другого ничем, кроме значения. Которых всего два.

    Кстати, я там выше ошибся на 3 десятичных порядка. Не через 600 млн, а 600 млрд лет в линии возникнет неотловленная алгоритмом ошибка. То есть через несколько больше, чем всё время существования вселенной.
    Кстати 2, классический модбас даст одну ошибку в месяц-два на средних линиях. Поэтому обычно для аналоговых сигналов медианный фильтр хотя бы на 3 элемента (или дублировать), а ответственные дискретные преспрашивать либо передавать в дублированном (троированном) виде.