|
CRC16 для modbus, не повторяйте ошибок |
|
|
|
Oct 9 2008, 17:05
|

неотягощённый злом
     
Группа: Свой
Сообщений: 2 746
Регистрация: 31-01-08
Из: Санкт-Петербург
Пользователь №: 34 643

|
Я наткнулся недавно на очередные грабли. Суть такая: Есть алгоритм для подсчёта CRC16 для Modbus: Цитата 1. Load a 16–bit register with FFFF hex (all 1’s). Call this the CRC register. 2. Exclusive OR the first 8–bit byte of the message with the low–order byte of the 16–bit CRC register, putting the result in the CRC register. 3. Shift the CRC register one bit to the right (toward the LSB), zero–filling the MSB. Extract and examine the LSB. 4. (If the LSB was 0): Repeat Step 3 (another shift). (If the LSB was 1): Exclusive OR the CRC register with the polynomial value 0xA001 (1010 0000 0000 0001). 5. Repeat Steps 3 and 4 until 8 shifts have been performed. When this is done, a complete 8–bit byte will have been processed. 6. Repeat Steps 2 through 5 for the next 8–bit byte of the message. Continue doing this until all bytes have been processed. 7. The final content of the CRC register is the CRC value. 8. When the CRC is placed into the message, its upper and lower bytes must be swapped as described below. И есть алгоритм для подсчёта CRC16 из WinAvr <util/crc16.h> написанный на ASM и оптимизированный. Код Optimized CRC-16 calculation.
Polynomial: x^16 + x^15 + x^2 + 1 (0xa001)<br> Initial value: 0xffff
This CRC is normally used in disk-drive controllers.
The following is the equivalent functionality written in C.
\code uint16_t crc16_update(uint16_t crc, uint8_t a) { int i;
crc ^= a; for (i = 0; i < 8; ++i) { if (crc & 1) crc = (crc >> 1) ^ 0xA001; else crc = (crc >> 1); }
return crc; } Вроде как одно и тоже, а нет! По доке на modbus сдвиг нужно делать до анализа младшего бита crc, а в приведённой цитате из WinAvr <util/crc16.h> это не так. Я 3 часа долбался - почему это вдруг у моих приборов RS-485 перестал работать. А это я перед отпуском решил "улучшить" CRC модуль и забыл об этом.
--------------------
“Будьте внимательны к своим мыслям - они начало поступков” (Лао-Цзы)
|
|
|
|
3 страниц
< 1 2 3
|
 |
Ответов
(30 - 36)
|
Oct 17 2008, 21:33
|

Нечётный пользователь.
     
Группа: Свой
Сообщений: 2 033
Регистрация: 26-05-05
Из: Бровари, Україна
Пользователь №: 5 417

|
Цитата(demiurg_spb @ Oct 10 2008, 22:58)  Подтвердите Ваши слова на практике: 0x01 0x6C 0x08 0xC6 - СrcL 0x0C - СrcH В этом случае от перемены мест слагаемых (СrcL<->СrcH) сумма не меняется и никогда не равна 0. Что-то мне кажется, что где-то вкралась ошибка. buf2[] = { 0x01, 0x6C, 0x08, 0x0C, 0xC6 };приведенным Вами же исходником (цитатой из модбаса?) crc = crc16_1(buf2, sizeof(buf2));даёт нулевую CRC. Это если ещё и в поток давать инверсию от рассчитанной CRC, то тогда независимо от содержимого блока при правильной CRC рассчёт CRC для блока вместе с той инвертированной CRC даст константу, не равную нулю (т.е. проверять CRC от всего надо не на нуль, а на константу, 0x01B0 для приведенной crc16_1() ).
--------------------
Ну, я пошёл… Если что – звоните…
|
|
|
|
|
Oct 18 2008, 05:48
|

неотягощённый злом
     
Группа: Свой
Сообщений: 2 746
Регистрация: 31-01-08
Из: Санкт-Петербург
Пользователь №: 34 643

|
crc16_1 - "неправильная" функция из доки на modbus, она даёт результат в big-endian формате. Цитата(ReAl @ Oct 18 2008, 01:33)  Это если ещё и в поток давать инверсию от рассчитанной CRC, то тогда независимо от содержимого блока при правильной CRC рассчёт CRC для блока вместе с той инвертированной CRC даст константу, не равную нулю (т.е. проверять CRC от всего надо не на нуль, а на константу, 0x01B0 для приведенной crc16_1() ). Вы меня окончательно запутали что на что надо проверять  Я хотел понять как получить 0 в данном конкретном случае и наконец понял. В протоколе сказано, что приёмник должен посчитать CRC посылки без учёта последних двух байт и сравнить их с принятой crc, подсчитанной передатчиком (последние два байта посылки). О инверсиях и нулях в данном мануале речи не ведётся. Цитата(ReAl @ Oct 18 2008, 01:33)  Что-то мне кажется, что где-то вкралась ошибка. Сделал как Вы предлагаете: считать CRC для всей посылки. Действительно было непонимание, а сайчас получилось, что CRC для всей последовательности байт (с учётом двух байт CRC) = 0. Так даже проще, но несколько дольше, чем предлагается в описании протокола modbus, т.к. длиннее на 2 байта. ReAl, спасибо за помощь!
--------------------
“Будьте внимательны к своим мыслям - они начало поступков” (Лао-Цзы)
|
|
|
|
|
Oct 18 2008, 07:22
|

Нечётный пользователь.
     
Группа: Свой
Сообщений: 2 033
Регистрация: 26-05-05
Из: Бровари, Україна
Пользователь №: 5 417

|
Цитата(demiurg_spb @ Oct 18 2008, 08:48)  crc16_1 - "неправильная" функция из доки на modbus, она даёт результат в big-endian формате. Это ничего не меняет - я привёл уже последовательнсть байт, а нуль остаётся нулём и при перестановке байт. big-endian-ность crc16_1() приводит только к тому, что в конце формирования буфера приходится написать Код *ptr++ = (uint8_t)((crc) >> 8); *ptr = (uint8_t)crc; а не Код *ptr++ = (uint8_t)crc; *ptr = (uint8_t)((crc) >> 8); Цитата(demiurg_spb @ Oct 18 2008, 08:48)  Вы меня окончательно запутали что на что надо проверять  Если бы в конце CRC "от crc16_1()" заносилась так: Код *ptr++ = (uint8_t) ~((crc) >> 8); *ptr = (uint8_t) ~crc; и если бы на приёме прогонять через crc16_1() и данные, и контрольный код, как "в случае с нулём" выше, то результат выходил бы не всегда 0x0000, а всегда 0x01B0, что есть остатком от деления 0xFFFF на полином CRC (с учётом endian-ности crc16_1() ). Записывая в последние два байта инверсию CRC мы добавляем это к остатку от деления всего "полинома сообщения" на CRC и таким образом получаем (CRC ^ ~CRC) = 0xFFFF. Потом на приёме мы вместо сравнения полученной CRC с инверсией двух последних байт обрабатываем на два байта больше и получаем эту константу 0x01B0. Но это так, к слову, просто тут было упомянуто, что в некоторых случаях в поток заносится не CRC, а инверсия, ну так в этом случае в конце будет не 0 :-).
--------------------
Ну, я пошёл… Если что – звоните…
|
|
|
|
|
Oct 18 2008, 10:40
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
Цитата(demiurg_spb @ Oct 18 2008, 10:33)  Осталось только узнать в чём практическая польза этого трюка? Принят ошибочный пакет, состоящий из одних нулей. Без применения инверсии CRC сойдется и придется делать дополнительную проверку на ноль содержимого всего пакета для отлова именно этого конкретного случая, а при использовании инверсии проверка не даст ожидаемую константу и пакет будет отброшен уже на стадии проверки CRC. Польза от инициализации начального значения отличной от нуля константой: Если таки действительно пакет из одних нулей может присутствовать в обмене, то в случае инициализации нулем потеря части этих нулей или вкрапление лишних из-за ошибок синхронизации никак не отразится на CRC. В случае инициализации константой CRC уже не сойдется.
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Oct 18 2008, 20:02
|
дятел
    
Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065

|
Цитата(demiurg_spb @ Oct 10 2008, 23:58)  Подтвердите Ваши слова на практике: Цитата Вы меня окончательно запутали что на что надо проверять Я хотел понять как получить 0 в данном конкретном случае и наконец понял. В протоколе сказано, что приёмник должен посчитать CRC посылки без учёта последних двух байт и сравнить их с принятой crc, подсчитанной передатчиком (последние два байта посылки). Чего-то Вы в итоге похоже сами напутали. Возьмем последовательность вот от сюда: http://www.simplymodbus.ca/FC15.htmи возмем такой код: Код #include <util/crc16.h>
/* Типа пример!
Force Multiple Coils (FC=15)
Request
This command is writing the contents of a series of 10 discrete coils from #20 to #29 to the slave device with address 17.
11 0F 0013 000A 02 CD01 BF0B
11: The Slave Address (17 = 11 hex) 0F: The Function Code (Force Multiple Coil, 15 = 0F hex) 0013: The Data Address of the first coil. (coil# 20 - 1 = 19 = 13 hex) 000A: The number of coils to written (10 = 0A hex) 02: The number of data bytes to follow (10 Coils / 8 bits per byte = 2 bytes) CD: Coils 20 - 27 (1100 1101) 01: Coils 27 - 29 (0000 0001) BF0B: The CRC (cyclic redundancy check) for error checking. */
unsigned char buff[11] = { 0x11, 0x0F, 0x00, 0x13, 0x00, 0x0A, 0x02, 0xCD, 0x01, 0xBF, 0x0B };
volatile unsigned short CRC = 0xFFFF; volatile unsigned short CRC1;
int main(void) { unsigned char i;
for (i = 0; i < 9; i++) { CRC = _crc16_update(CRC, buff[i]); } CRC1 = CRC;
asm("nop"); // <-------- вот здесь CRC1 == 0x0BBF
CRC = _crc16_update(CRC, buff[9]); CRC = _crc16_update(CRC, buff[10]);
asm("nop"); // <--------- вот здесь CRC == 0
while (1); return 0; } То есть при формировании пакета с CRC мы должны сначала положить CRCLo == 0xBF, затем CRCHi == 0x0B. Полный CRC пакета дает 0.
|
|
|
|
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|