Мы сталкиваемся с задачей, которая на первый взгляд кажется простой – необходимо вычислить контрольную сумму CRC-16 Modbus. Однако, как показывает практика, даже при наличии формулы и онлайн-калькуляторов, получить корректный результат в 1С бывает не так просто. Часто возникают сомнения в правильности реализации алгоритма, особенно когда полученный результат визуально отличается от эталонного, хотя по сути они идентичны. Давайте вместе разберем эту ситуацию и создадим надежное решение для 1С.
Многие из нас могли столкнуться с ситуацией, когда, получив результат 4B37 от своей функции в 1С, и сравнив его с результатом онлайн-калькулятора 0x4B37 для одной и той же тестовой строки "123456789", возникало замешательство. На самом деле, 4B37 и 0x4B37 – это одно и то же число, представленное в шестнадцатеричном формате. Но это не отменяет необходимости иметь четкое и корректное решение для расчета CRC-16 Modbus в 1С. Мы проанализируем исходный код, выявим его слабые места и предложим улучшенную, современную реализацию.
Прежде чем погружаться в код, давайте рассмотрим подробнее, что такое CRC-16 Modbus и каковы его стандартные параметры. Понимание этих основ критически важно для правильной реализации.
0xA001. Это значение соответствует полиному, указанному в исходном примере.0xFFFF.True (биты в каждом входном байте отражаются перед обработкой).True (биты в конечном CRC-регистре отражаются после обработки всех данных).0x0000 (никаких дополнительных XOR-операций с конечным результатом не производится).Общий алгоритм включает следующие шаги:
0xFFFF.XOR между текущим байтом данных и младшим байтом CRC-регистра.XOR с полиномом 0xA001.В протоколе Modbus RTU, после вычисления 16-битного CRC, он передается в сообщении сначала младшим байтом, затем старшим байтом. Это означает, что если вычисленное CRC равно 0x4B37, то в сообщении сначала будет отправлен байт 0x37, а затем 0x4B.
CRC-16 Modbus используется для обеспечения целостности данных в коммуникациях Modbus RTU. Он помогает обнаруживать ошибки, возникающие во время передачи данных.
Давайте разберем по шагам исходный пример функции, который был предложен, и выявим, что можно улучшить, чтобы сделать его более современным и понятным.
// Функция сдвига вправо, т.к. в 1С нет оператора >>
Функция СдвигВправо(Число, КолВоБит)
Возврат Цел(Число / (2 ^ КолВоБит));
КонецФункции
// Функция расчёта CRC-16 Modbus
Функция CRC16Modbus(Данные)
// Данные — строка в ANSI (каждый символ — байт)
CRC = 16#FFFF;
Полином = 16#A001;
Для i = 1 По СтрДлина(Данные) Цикл
Байта = КодСимвола(Сред(Данные, i, 1));
CRC = CRC XOR Байта;
Для j = 1 По 8 Цикл
Если (CRC & 1) = 1 Тогда
CRC = СдвигВправо(CRC, 1) XOR Полином;
Иначе
CRC = СдвигВправо(CRC, 1);
КонецЕсли;
КонецЦикла;
КонецЦикла;
Возврат CRC;
КонецФункции
Что мы видим в этом коде?
СдвигВправо: Это устаревшая реализация побитового сдвига. Начиная с версии 8.3.11, в 1С доступны встроенные побитовые операции, включая ПобитовыйСдвигВправо. Использование встроенных функций всегда предпочтительнее, так как они более производительны и надежны.Байта = КодСимвола(Сред(Данные, i, 1)); предполагает, что входная строка Данные является "ANSI-строкой", где каждый символ соответствует одному байту данных. Для тестовой строки "123456789", где мы хотим посчитать CRC от ASCII-кодов символов (например, '1' это 49, '2' это 50 и т.д.), этот подход является корректным. Однако, если бы входные данные были представлены в виде последовательности шестнадцатеричных значений (например, "0110C0030001"), то пришлось бы сначала преобразовать их в соответствующие байты, а затем уже использовать их в расчете. Для большинства случаев, когда входные данные являются текстовой строкой, КодСимвола работает как ожидается.0xA001, начальное значение 0xFFFF, побитовые операции сдвига и XOR) реализован верно. Именно поэтому тестовый результат 4B37 оказался правильным.Теперь, когда мы понимаем основы и выявили, что можно улучшить, давайте создадим обновленную функцию для расчета CRC-16 Modbus, используя современные возможности платформы 1С.
Мы заменим пользовательскую функцию СдвигВправо на встроенную ПобитовыйСдвигВправо. Это значительно упростит код и повысит его эффективность.
// Функция расчёта CRC-16 Modbus
// Данные — строка, каждый символ которой представляет собой байт данных для расчёта CRC.
// Например, для строки "123456789", будут использоваться ASCII-коды символов.
Функция CRC16Modbus_Современный(Данные)
// Инициализируем CRC-регистр стандартным значением для Modbus
Перем CRC;
CRC = 16#FFFF;
// Стандартный полином для CRC-16 Modbus (отраженный)
Перем Полином;
Полином = 16#A001;
// Перебираем каждый байт (символ) входных данных
Для i = 1 По СтрДлина(Данные) Цикл
// Получаем ASCII-код текущего символа.
// Это и будет байт данных для расчета.
Перем БайтДанных;
БайтДанных = КодСимвола(Сред(Данные, i, 1));
// Выполняем XOR между текущим CRC и байтом данных
CRC = CRC XOR БайтДанных;
// Выполняем 8 итераций для каждого бита байта
Для j = 1 По 8 Цикл
// Проверяем младший бит CRC
Если (CRC & 1) = 1 Тогда
// Если младший бит равен 1, сдвигаем CRC вправо и XOR'им с полиномом
CRC = ПобитовыйСдвигВправо(CRC, 1) XOR Полином;
Иначе
// Если младший бит равен 0, просто сдвигаем CRC вправо
CRC = ПобитовыйСдвигВправо(CRC, 1);
КонецЕсли;
КонецЦикла;
КонецЦикла;
Возврат CRC;
КонецФункции
Давайте посмотрим на пример обновленного кода и разберем каждый его шаг, чтобы лучше понять логику вычисления CRC-16 Modbus.
Мы начинаем с инициализации 16-битного регистра CRC значением 16#FFFF и переменной Полином значением 16#A001. Это стандартные параметры для CRC-16 Modbus.
CRC = 16#FFFF;
Полином = 16#A001;
Мы используем цикл Для i = 1 По СтрДлина(Данные) Цикл для итерации по каждому символу входной строки Данные. Каждый символ рассматривается как отдельный байт.
Внутри цикла мы получаем числовое значение байта с помощью функции КодСимвола:
БайтДанных = КодСимвола(Сред(Данные, i, 1));
Затем этот БайтДанных XOR'ится с текущим значением CRC:
CRC = CRC XOR БайтДанных;
Для каждого обработанного байта данных выполняется внутренний цикл Для j = 1 По 8 Цикл. Этот цикл обрабатывает каждый из 8 бит текущего байта данных, влияя на регистр CRC.
Ключевой момент здесь — проверка младшего бита CRC с помощью побитовой операции (CRC & 1):
Если (CRC & 1) = 1 Тогда
// ...
Иначе
// ...
КонецЕсли;
CRC равен 1 (условие (CRC & 1) = 1 истинно), это означает, что произошел "перенос". В этом случае CRC сначала сдвигается вправо на 1 бит с помощью ПобитовыйСдвигВправо(CRC, 1), а затем результат XOR'ится с Полином.CRC равен 0, то CRC просто сдвигается вправо на 1 бит, без XOR с полиномом.
CRC = ПобитовыйСдвигВправо(CRC, 1) XOR Полином; // Если младший бит = 1
// или
CRC = ПобитовыйСдвигВправо(CRC, 1); // Если младший бит = 0
Этот процесс повторяется 8 раз, моделируя деление на полином.
После обработки всех байтов данных, функция возвращает окончательное 16-битное значение CRC.
Возврат CRC;
Теперь, когда у нас есть готовая функция, давайте посмотрим на пример ее вызова и проверим результаты с известными тестовыми данными.
Для стандартной тестовой строки "123456789" мы ожидаем получить CRC-16 Modbus равное 0x4B37.
ДанныеДляРасчета = "123456789"; // Стандартный тестовый набор для CRC
CRC_Результат = CRC16Modbus_Современный(ДанныеДляРасчета);
// Выведем результат в шестнадцатеричном формате
Сообщить("CRC-16 Modbus для '" + ДанныеДляРасчета + "': " + Формат(CRC_Результат, "X4"));
// Ожидаемый вывод: CRC-16 Modbus для '123456789': 4B37
Как мы видим, результат 4B37 полностью совпадает с эталонным. Таким образом, наша функция работает корректно.
Для дополнительной проверки, рассмотрим еще один пример. Для последовательности байтов 01 10 C0 03 00 01 (в шестнадцатеричном виде) CRC-16 Modbus должно быть 0xC9CD.
Чтобы передать такую последовательность байтов в нашу функцию, нам нужно создать строку, где каждый символ будет представлять собой соответствующий байт. Мы можем использовать функцию Символ() для этого:
// Создаем строку из байтов: 0x01, 0x10, 0xC0, 0x03, 0x00, 0x01
// Символ(1) -> 0x01
// Символ(16) -> 0x10
// Символ(192) -> 0xC0
// Символ(3) -> 0x03
// Символ(0) -> 0x00
// Символ(1) -> 0x01
ДанныеДляРасчета2 = Символ(1) + Символ(16) + Символ(192) + Символ(3) + Символ(0) + Символ(1);
CRC_Результат2 = CRC16Modbus_Современный(ДанныеДляРасчета2);
Сообщить("CRC-16 Modbus для байтов 01 10 C0 03 00 01: " + Формат(CRC_Результат2, "X4"));
// Ожидаемый вывод: CRC-16 Modbus для байтов 01 10 C0 03 00 01: C9CD
Этот пример подтверждает универсальность нашей функции для различных типов входных данных, представленных в виде "байтовой" строки.
При работе с CRC-16 Modbus в 1С выясним несколько важных моментов, которые помогут избежать распространенных ошибок:
Данные представляет собой один байт, то есть его КодСимвола() является требуемым значением байта. Если вы работаете с текстовыми данными, которые могут содержать символы вне диапазона ASCII (например, кириллица в UTF-8), то КодСимвола() вернет Unicode-код символа, а не байтовое представление. В таких случаях необходимо сначала преобразовать исходные данные в массив байтов или в строку, где каждый символ точно соответствует одному байту (например, используя кодировку ANSI или ДвоичныеДанные).ПобитовыйСдвигВправо, XOR, & доступны в 1С, начиная с версии 8.3.11. Убедитесь, что ваша платформа соответствует этому требованию.0x4B37, то для отправки по Modbus RTU сначала нужно отправить байт 0x37, а затем 0x4B. Это не влияет на расчет самого CRC, но критично для формирования конечного сообщения.Мы рассмотрели проблему расчета CRC-16 Modbus в 1С, проанализировали исходный код, улучшили его с использованием современных возможностей платформы и подробно объяснили каждый шаг алгоритма. Теперь у вас есть надежное и проверенное решение для вашей задачи!
← К списку