реклама на сайте
подробности

 
 
> CRC16 для modbus, не повторяйте ошибок
demiurg_spb
сообщение Oct 9 2008, 17:05
Сообщение #1


неотягощённый злом
******

Группа: Свой
Сообщений: 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 модуль и забыл об этом.


--------------------
“Будьте внимательны к своим мыслям - они начало поступков” (Лао-Цзы)
Go to the top of the page
 
+Quote Post
3 страниц V  < 1 2 3  
Start new topic
Ответов (30 - 36)
ReAl
сообщение Oct 17 2008, 21:33
Сообщение #31


Нечётный пользователь.
******

Группа: Свой
Сообщений: 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() ).


--------------------
Ну, я пошёл… Если что – звоните…
Go to the top of the page
 
+Quote Post
demiurg_spb
сообщение Oct 18 2008, 05:48
Сообщение #32


неотягощённый злом
******

Группа: Свой
Сообщений: 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() ).

Вы меня окончательно запутали что на что надо проверятьsmile.gif
Я хотел понять как получить 0 в данном конкретном случае и наконец понял.
В протоколе сказано, что приёмник должен посчитать CRC посылки без учёта последних двух байт и сравнить их с принятой crc, подсчитанной передатчиком (последние два байта посылки).

О инверсиях и нулях в данном мануале речи не ведётся.

Цитата(ReAl @ Oct 18 2008, 01:33) *
Что-то мне кажется, что где-то вкралась ошибка.

Сделал как Вы предлагаете: считать CRC для всей посылки.

Действительно было непонимание, а сайчас получилось, что CRC для всей последовательности байт (с учётом двух байт CRC) = 0.
Так даже проще, но несколько дольше, чем предлагается в описании протокола modbus,
т.к. длиннее на 2 байта.

ReAl, спасибо за помощь!


--------------------
“Будьте внимательны к своим мыслям - они начало поступков” (Лао-Цзы)
Go to the top of the page
 
+Quote Post
ReAl
сообщение Oct 18 2008, 07:22
Сообщение #33


Нечётный пользователь.
******

Группа: Свой
Сообщений: 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) *
Вы меня окончательно запутали что на что надо проверятьsmile.gif
Если бы в конце 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 :-).


--------------------
Ну, я пошёл… Если что – звоните…
Go to the top of the page
 
+Quote Post
demiurg_spb
сообщение Oct 18 2008, 07:33
Сообщение #34


неотягощённый злом
******

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



Цитата(ReAl @ Oct 18 2008, 11:22) *
Но это так, к слову, просто тут было упомянуто, что в некоторых случаях в поток заносится не CRC, а инверсия, ну так в этом случае в конце будет не 0 :-).

Как я понял, случаи бывают разныеsmile.gif
Осталось только узнать в чём практическая польза этого трюка?


--------------------
“Будьте внимательны к своим мыслям - они начало поступков” (Лао-Цзы)
Go to the top of the page
 
+Quote Post
Сергей Борщ
сообщение Oct 18 2008, 10:40
Сообщение #35


Гуру
******

Группа: Модераторы
Сообщений: 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)
Go to the top of the page
 
+Quote Post
singlskv
сообщение Oct 18 2008, 20:02
Сообщение #36


дятел
*****

Группа: Свой
Сообщений: 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.
Go to the top of the page
 
+Quote Post
demiurg_spb
сообщение Oct 19 2008, 09:39
Сообщение #37


неотягощённый злом
******

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



Цитата(singlskv @ Oct 19 2008, 00:02) *
Чего-то Вы в итоге похоже сами напутали.
То есть при формировании пакета с CRC мы должны сначала положить CRCLo == 0xBF,
затем CRCHi == 0x0B.
Полный CRC пакета дает 0.

Всё правильно. Я уже со всем разобрался.
Спасибо.


--------------------
“Будьте внимательны к своим мыслям - они начало поступков” (Лао-Цзы)
Go to the top of the page
 
+Quote Post

3 страниц V  < 1 2 3
Reply to this topicStart new topic
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

 


RSS Текстовая версия Сейчас: 27th June 2025 - 15:07
Рейтинг@Mail.ru


Страница сгенерированна за 0.01487 секунд с 7
ELECTRONIX ©2004-2016