Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: CRC16(ModBus) на С для ATmega128
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > AVR
sf9
Всем доброе время суток!
Прошу помочь мне разобраться в С коде.Сам только недавно начал осваивать.
нужно реализовать CRC16 для ModBus.
Я знаю,что есть куча готовых реализаций.Простым(последовательным) и табличным способом.
Но хочу реализовать сам.
У меня проблема в том,что для вычисления CRC нужно смотреть на состояние младшого бита. Если "1" - то выполнять XOR,если нет,то крутить дальше.

Вот мой код:

int crc16 (unsigned char a) // a - 8-bit data for calculation CRC
{
unsigned int reg, tmp;
int i;

reg = 0xFFFF; // step1: initial loading of 16-bit register
reg ^= (unsigned int)a; // step2: a XOR reg

for(i = 0;i<8;i++) //step3: 8-iteration to calculate CRC
{
reg >>= 1;
tmp = reg;
tmp <<= 15;
if(tmp == 0x8000) reg ^= 0xA001; // checking: if LSB = "1" reg=reg^0xA001
}
return reg;
}

проверку я выолняю поразрядным сдвигом 16-битного reg влево, и, если там "1",то xor.

ниже преведена другая реализация CRC16 и она работет корректно!!!!

int crc16 (unsigned char a)
{
unsigned int reg;
unsigned char i, tmp;

reg = 0xFFFF;
reg ^= (unsigned int)a;
for(i = 0;i<8;i++)
{
tmp = (unsigned char)(reg&0x0001);
reg >>= 1;
if(tmp) reg ^= 0xA001;
}
return reg;
}

Поясните,что не так в первой реализачии. smile3046.gif
Sat360
В первой реализации регистр сдвигается ДО анализа бита. Следовательно, младший бит теряется.
Сергей Борщ
В работающей программе сначала запоминается младший бит, потом делается сдвиг вправо (при этом в сдвинутом числе этот бит уже потерян). Вы же сначала сдвигаете вправо, потом делаете проверку. Т.е. проверяете не младший, а следующий бит. Ваш сдвиг влево на 15 разрядов не нужен:
Код
    tmp <<= 15;
    if(tmp == 0x8000)
эквивалентно
    if(tmp & 1)
Этот код более красиво можно записать так:
Код
int crc16 (uinsigned int crc, unsigned char a)         // a - 8-bit data for calculation CRC
{
            int i;
    
    crc ^= (unsigned int)a;    // step2: a XOR reg
    
    for(i = 0;i<8;i++)           //step3: 8-iteration to calculate CRC  
    {
        if(crc & 1)
        {
            crc >>= 1; reg ^= 0xA001;
        }
        else
        {
            crc >>= 1;
        }
    }
    return crc;
}
Обратите внимание, что 0xFFFF заносится в crc один раз перед обсчетом всего массива.

P.S. В форме ввода сообщения есть кнопочка для оформления кода. Она обозначена символом "#". Используйте ее, пожалуйста, при публикации кода. Иначе портится форматирование и ваш исходник становится трудночитаемым.
sf9
СПАСИБО ОГРОМНОЕ!!! a14.gif
Я понял,просто дело в том,что я не соблюдал порядок выполнения кода.
Свой корявый вариант я подправил:
Код
int crc16 (unsigned char a)
{
    unsigned int reg,tmp;
    unsigned int i;
    
    reg = 0xFFFF;                 
    reg ^= (unsigned int)a;    
    
    for(i = 0;i<8;i++)
    {
        tmp = reg;
        reg >>= 1;        
        if((tmp <<=15)==0x8000) reg ^= 0xA001;
    }
    return reg;
}


А если с Вашими комментариями получилось расчудесно:
Код
        if((tmp <<=15)==0x8000) reg ^= 0xA001;
меняем на
        if(tmp&1) reg ^= 0xA001;


И ещё хотел бы узнать,как можно напрямую обращаться к биту в байте на языке С?
??
Rst7
А меня вот последнее время поперло по-другому CRC16 для модбаса считать. Оптимизированно под IAR AVR (чтобы все в регистрах было)
Код
__z UREG CRC16stage2(UINT8 uchCRCLo, UINT8 uchCRCHi, UREG l, UREG write, UINT8 *puchMsg, UREG i64, UREG i128, UREG i1, UREG i192);

UREG CRC16(UINT8 *puchMsg, UREG usDataLen, UREG write )
{
  return CRC16stage2(0xFF,0xFF,usDataLen,write,puchMsg,64,128,1,192);
}

#pragma optimize=no_inline
__z UREG CRC16stage2(UINT8 uchCRCLo, UINT8 uchCRCHi, UREG l, UREG write, UINT8 *puchMsg, UREG i64, UREG i128, UREG i1, UREG i192)
{
  do
  {
    UINT8 parity;
    UINT16 tmp16;
    parity=(uchCRCLo ^= *puchMsg++);
    uchCRCLo=uchCRCHi;
    tmp16=__multiply_unsigned(parity,i64);
    uchCRCHi=tmp16>>8;
    uchCRCLo^=tmp16;
    tmp16=__multiply_unsigned(parity,i128);
    uchCRCHi^=tmp16>>8;
    uchCRCLo^=tmp16;
    parity^=__swap_nibbles(parity);
    parity^=parity>>1;
    if (!(parity & 4)) parity--;
    if (!(parity&1))
    {
      uchCRCLo^=i1;
      uchCRCHi^=i192;
    }
  }
  while(--l);
  if (write)
  {
    *puchMsg++=uchCRCLo;
    *puchMsg++=uchCRCHi;
  }
  else
  {
    UREG k;
    if ((k=*puchMsg++-uchCRCLo)) return k;
    return *puchMsg++-uchCRCHi;
  }
  return 0;  
}


и результат
Код
        RSEG CODE:CODE:NOROOT(1)
//   74 UREG CRC16(UINT8 *puchMsg, UREG usDataLen, UREG write )
CRC16:
//   75 {
        MOVW    R31:R30, R17:R16
//   76   return CRC16stage2(0xFF,0xFF,usDataLen,write,puchMsg,64,128,1,192);
        LDI     R23, 192
        LDI     R22, 1
        LDI     R21, 128
        LDI     R20, 64
        LDI     R17, 255
        LDI     R16, 255
        REQUIRE CRC16stage2
       ;               // Fall through to label CRC16stage2
//   77 }
//   78
//   79 #pragma optimize=no_inline

        RSEG CODE:CODE:NOROOT(1)
//   80 __z UREG CRC16stage2(UINT8 uchCRCLo, UINT8 uchCRCHi, UREG l, UREG write, UINT8 *puchMsg, UREG i64, UREG i128, UREG i1, UREG i192)
CRC16stage2:
//   81 {
        ST      -Y, R24
        MOV     R3, R16
        MOV     R2, R17
        MOV     R24, R22
//   82   do
//   83   {
//   84     UINT8 parity;
//   85     UINT16 tmp16;
//   86     parity=(uchCRCLo ^= *puchMsg++);
??CRC16stage2_0:
        LD      R16, Z+
        EOR     R3, R16
        MOV     R22, R3
//   87     uchCRCLo=uchCRCHi;
        MOV     R3, R2
//   88     tmp16=__multiply_unsigned(parity,i64);
        MUL     R22, R20
//   89     uchCRCHi=tmp16>>8;
        MOV     R2, R1
//   90     uchCRCLo^=tmp16;
        EOR     R3, R0
//   91     tmp16=__multiply_unsigned(parity,i128);
        MUL     R22, R21
//   92     uchCRCHi^=tmp16>>8;
        EOR     R2, R1
//   93     uchCRCLo^=tmp16;
        EOR     R3, R0
//   94     parity^=__swap_nibbles(parity);
        MOV     R16, R22
        SWAP    R16
        EOR     R22, R16
//   95     parity^=parity>>1;
        MOV     R16, R22
        LSR     R16
        EOR     R22, R16
//   96     if (!(parity & 4)) parity--;
        BST     R22, 2
        BRTS    ??CRC16stage2_1
        DEC     R22
//   97     if (!(parity&1))
??CRC16stage2_1:
        BST     R22, 0
        BRTS    ??CRC16stage2_2
//   98     {
//   99       uchCRCLo^=i1;
        EOR     R3, R24
//  100       uchCRCHi^=i192;
        EOR     R2, R23
//  101     }
//  102   }
//  103   while(--l);
??CRC16stage2_2:
        DEC     R18
        BRNE    ??CRC16stage2_0
//  104   if (write)
        TST     R19
        BREQ    ??CRC16stage2_3
//  105   {
//  106     *puchMsg++=uchCRCLo;
        ST      Z+, R3
//  107     *puchMsg++=uchCRCHi;
        ST      Z, R2
//  108   }
//  109   else
//  110   {
//  111     UREG k;
//  112     if ((k=*puchMsg++-uchCRCLo)) return k;
//  113     return *puchMsg++-uchCRCHi;
//  114   }
//  115   return 0;  
        LDI     R16, 0
        RJMP    ??CRC16stage2_4
??CRC16stage2_3:
        LD      R16, Z+
        SUB     R16, R3
        BRNE    ??CRC16stage2_4
        LD      R16, Z
        SUB     R16, R2
??CRC16stage2_4:
        LD      R24, Y+
        RET
//  116 }


Итого - 29 тактов на байт. И без всяких таблиц.

Кстати, вариант
Код
__x UREG CRC16stage2(UINT8 uchCRCLo, UINT8 uchCRCHi, UREG l, UREG write, UINT8 *puchMsg, UREG i64, UREG i128, UREG i1, UREG i192);

__x UREG CRC16(UINT8 *puchMsg, UREG usDataLen, UREG write )
{
  return CRC16stage2(0xFF,0xFF,usDataLen,write,puchMsg,64,128,1,192);
}

#pragma optimize=no_inline
__x UREG CRC16stage2(UINT8 uchCRCLo, UINT8 uchCRCHi, UREG l, UREG write, UINT8 *puchMsg, UREG i64, UREG i128, UREG i1, UREG i192)
...

сохранит 1 байт CSTACK, но может ухудшить верхний код.
sf9
Кстати,в компиляторах и САПРах для МСs как можно подсчитать кол-во тактов,оптимизацию и пр.?
В частности,AVRstudio???????
Rst7
Никак. Только ручками. Ну или в симуляторе выполнить код и посмотреть.

А вот "подсчитать оптимизацию" - это Вы что имели в виду?
sf9
Цитата(Rst7 @ Sep 17 2008, 13:29) *
Никак. Только ручками. Ну или в симуляторе выполнить код и посмотреть.

А вот "подсчитать оптимизацию" - это Вы что имели в виду?


Согласен,некорректно выразился.
Хотел спросить следующее:"Есть ли настройки в компиляторах или САПРах для МК,которые позволяют оптимизировать проект в той или иной области - памяти программ или данных?"
Или всё же только ручками?
smile3046.gif
Rst7
Цитата
Есть ли настройки в компиляторах или САПРах для МК,которые позволяют оптимизировать проект


Ну обычно говорят не САПР, а среда разработки. Но то не суть.

У всех вменяемых компиляторов есть возможность настройки уровня и типа оптимизации. Как для всего проекта, так и для отдельных функций (например в моем примере #pragma optimize=no_inline - это для того, чтобы функция никогда не включалась во внутрь другой).

Кроме того, зная особенности поведения компилятора, можно помочь ему руками - написав соответствующий код, но тут только опыт поможет wink.gif
sf9
07.gif
Спасибо большое!
Побрёл я наращивать головные мышцы.И извилины развивать.
ReAl
Цитата(Rst7 @ Sep 17 2008, 11:52) *
А меня вот последнее время поперло по-другому CRC16 для модбаса считать. Оптимизированно под IAR AVR (чтобы все в регистрах было)
У avr-gcc в util/crc16.h есть под несколько разных полиномов такие быстрые алгоритмы в виде асм-вставок для добавления к crc одного байта, хорошо вставляются внутрь своего цикла.
А у альтеры апнота была на тему fast crc calculation, где разрисовано красиво откуда ноги растут у такой оптимизации и по той апноте можно сделать для любого полинома.



Цитата(ReAl @ Sep 17 2008, 13:07) *
У avr-gcc в util/crc16.h есть под несколько разных полиномов такие быстрые алгоритмы в виде асм-вставок для добавления к crc одного байта, хорошо вставляются внутрь своего цикла.
Хм.. там сдвиги не умножением сделаны, а впрямую, но всё равно в зависимости от полинома уже находящийся в регистре байт добавляется к находящейся на регистрах CRC за 17..25 тактов в зависимости от полинома. В цикле будет плюс загрузка очередного байта и организация цикла, в итоге по времени выйдет на приблизительно то же, что и с умножением. Но будет работать и на мелких кристаллах.
singlskv
Цитата(ReAl @ Sep 17 2008, 14:29) *
У avr-gcc в util/crc16.h есть под несколько разных полиномов такие быстрые алгоритмы в виде асм-вставок для добавления к crc одного байта, хорошо вставляются внутрь своего цикла.
Хм.. там сдвиги не умножением сделаны, а впрямую, но всё равно в зависимости от полинома уже находящийся в регистре байт добавляется к находящейся на регистрах CRC за 17..25 тактов в зависимости от полинома.

+1 Оптимальнее чем в avr-gcc для crc16 и не сделать, поэтому всегда пользуюсь встроенным...
sf9
По поводу crc16.h абсолютно согласен,вчера проверял,тестировал,всё работает быстрее,чем другие реализации. Ну и,естественно,удобно. wink.gif
Rst7
Цитата
+1 Оптимальнее чем в avr-gcc для crc16 и не сделать


Не кажи "гоп" wink.gif

Код
__z UREG CRC16stage2(UINT8 uchCRCLo, UINT8 uchCRCHi, UREG l, UREG write, UINT8 *puchMsg, UREG i128,UREG i1, UREG i192);

UREG CRC16(UINT8 *puchMsg, UREG usDataLen, UREG write )
{
  return CRC16stage2(0xFF,0xFF,usDataLen,write,puchMsg,128,1,192);
}

#pragma optimize=no_inline
__z UREG CRC16stage2(UINT8 uchCRCLo, UINT8 uchCRCHi, UREG l, UREG write, UINT8 *puchMsg, UREG i128, UREG i1, UREG i192)
{
  do
  {
    UINT8 parity;
    UINT16 tmp16;
    parity=*puchMsg++;
    parity^=uchCRCLo;
    uchCRCLo=uchCRCHi;
    tmp16=__multiply_unsigned(parity,i128);
    uchCRCHi=tmp16>>8;
    uchCRCLo^=tmp16;
    tmp16>>=1;
    uchCRCHi^=tmp16>>8;
    uchCRCLo^=tmp16;
    parity^=tmp16>>8;
    parity^=parity>>1;
    parity^=__swap_nibbles(parity);
    if (parity&1)
    {
      uchCRCLo^=i1;
      uchCRCHi^=i192;
    }
  }
  while(--l);
  if (write)
  {
    *puchMsg++=uchCRCLo;
    *puchMsg++=uchCRCHi;
  }
  else
  {
    UREG k;
    if ((k=*puchMsg++-uchCRCLo)) return k;
    return *puchMsg++-uchCRCHi;
  }
  return 0;  
}


Код
        RSEG CODE:CODE:NOROOT(1)
//   74 UREG CRC16(UINT8 *puchMsg, UREG usDataLen, UREG write )
CRC16:
//   75 {
        MOVW    R31:R30, R17:R16
//   76   return CRC16stage2(0xFF,0xFF,usDataLen,write,puchMsg,128,1,192);
        LDI     R22, 192
        LDI     R21, 1
        LDI     R20, 128
        LDI     R17, 255
        LDI     R16, 255
        REQUIRE CRC16stage2
       ;               // Fall through to label CRC16stage2
//   77 }
//   78
//   79 #pragma optimize=no_inline

        RSEG CODE:CODE:NOROOT(1)
//   80 __z UREG CRC16stage2(UINT8 uchCRCLo, UINT8 uchCRCHi, UREG l, UREG write, UINT8 *puchMsg, UREG i128, UREG i1, UREG i192)
CRC16stage2:
//   81 {
        MOV     R2, R16
        MOV     R23, R17
//   82   do
//   83   {
//   84     UINT8 parity;
//   85     UINT16 tmp16;
//   86     parity=*puchMsg++;
??CRC16stage2_0:
        LD      R3, Z+
//   87     parity^=uchCRCLo;
        EOR     R3, R2
//   88     uchCRCLo=uchCRCHi;
        MOV     R2, R23
//   89     tmp16=__multiply_unsigned(parity,i128);
        MUL     R3, R20
//   90     uchCRCHi=tmp16>>8;
        MOV     R23, R1
//   91     uchCRCLo^=tmp16;
        EOR     R2, R0
//   92     tmp16>>=1;
        LSR     R1
        ROR     R0
        EOR     R23, R1
//   93     uchCRCHi^=tmp16>>8;
//   94     uchCRCLo^=tmp16;
        EOR     R2, R0
//   95     parity^=tmp16>>8;
        EOR     R3, R1
//   96     parity^=parity>>1;
        MOV     R16, R3
        LSR     R16
        EOR     R3, R16
//   97     parity^=__swap_nibbles(parity);
        MOV     R16, R3
        SWAP    R16
        EOR     R3, R16
//   98     if (parity&1)
        BST     R3, 0
        BRTC    ??CRC16stage2_1
//   99     {
//  100       uchCRCLo^=i1;
        EOR     R2, R21
//  101       uchCRCHi^=i192;
        EOR     R23, R22
//  102     }
//  103   }
//  104   while(--l);
??CRC16stage2_1:
        DEC     R18
        BRNE    ??CRC16stage2_0
//  105   if (write)
        TST     R19
        BREQ    ??CRC16stage2_2
//  106   {
//  107     *puchMsg++=uchCRCLo;
        ST      Z+, R2
//  108     *puchMsg++=uchCRCHi;
        ST      Z, R23
//  109   }
//  110   else
//  111   {
//  112     UREG k;
//  113     if ((k=*puchMsg++-uchCRCLo)) return k;
//  114     return *puchMsg++-uchCRCHi;
//  115   }
//  116   return 0;  
        LDI     R16, 0
        RET
??CRC16stage2_2:
        LD      R16, Z+
        SUB     R16, R2
        BRNE    ??CRC16stage2_3
        LD      R16, Z
        SUB     R16, R23
??CRC16stage2_3:
        RET
//  117 }


20.5 тактов против 23 на собственно расчет следующего значения CRC16.
singlskv
Цитата(Rst7 @ Sep 18 2008, 15:42) *
Не кажи "гоп" wink.gif
Ok, согласен
умножение здесь рулит...
Только в любом случае в предыдущем варианте было хуже чем в winavr...
Ну и еще, этот вариант просто непременим в winavr, поэтому никогда и не задумывался
об улучшении с 23 тактов...

а в чистом асм возможно 1-2 такта можно было-бы еще выиграть, но нужно много думать...
=GM=
А что если сделать две таблицы на биты <7-4> и <3-0>? Прикинул программулю, получается 20 тактов на байт, размер 47 слов вместе с таблицами.
Сергей Борщ
Цитата(=GM= @ Sep 19 2008, 14:20) *
А что если... Прикинул программулю, получается 20 тактов на байт
Так оно работает или нет? Если нет, то какой прок от таких тактов? Если да - поделитесь кодом, интересно будет многим.
=GM=
Идея простая, как веник. Ну вот как-то так, таблица должна быть настроена на границу 256 байт, можно и 64.
Код
        lxi     yh,high(crctable)
        mov     yl,data
        ani     yl,0x0F
        ld      tmp,y+
        eor     crcl,tmp
        ld      tmp,y+
        eor     crch,tmp
        mov     yl,data
        swap    yl
        ani     yl,0x0F
        sbci    yl,-32
        ld      tmp,y+
        eor     crcl,tmp
        ld      tmp,y+
        eor     crch,tmp
ReAl
Цитата(=GM= @ Sep 19 2008, 15:07) *
Идея простая, как веник. Ну вот как-то так, таблица должна быть настроена на границу 256 байт, можно и 64.
И что, именно этот веник действительно метёт?
Сколько я не смотрел в сторону 4-битной табличной реализации, она ну никак не выходила быстрее реализаций, подобных приведенной тут либо в util/crc.h.
Rst7
Цитата
Идея простая, как веник.


Только таблички в ОЗУ надо хранить. А это вечно недостающий ресурс.

Ну и пара огрехов других, например, надо сдвигать yl влево на один бит. Ну или y+ заменить на y и subi yl,0-16 - это тоже тактов не добавит.
ReAl
Цитата(Rst7 @ Sep 19 2008, 19:06) *
Ну и пара огрехов других, например, надо сдвигать yl влево на один бит.
Это ладно, я не это имел ввиду, когда писал

Цитата(ReAl @ Sep 19 2008, 18:16) *
И что, именно этот веник действительно метёт?


Даже если исправить эти мелкие огрехи, в переводе на С тот ассемблерный кусок выглядит приблизительно так:

Код
extern uint16_t crctable[32];
uint16_t crc;

void crc_update(uint8_t data) {
    crc ^= crctable[ data & 0x0F ];
    crc ^= crctable[ 16 + (data >>4) ];
}


Что-то не пахнет тут CRC.
Мне кажется, что это ничем не лучше простой XORки всех байтов сообщения.
Независимо от таблицы реакция на последовательность 0xA5 0xC3 будет та же, что и на
0xC3 0xA5 или на 0xC5 0xA3.

А после того, как заставить этот код считать таки CRC, да табличку положить во флеш и получить +4 такта на переходе от LD к LPM - выйдет дольше, чем обсуждавшиеся методы.
=GM=
Цитата(ReAl @ Sep 19 2008, 19:27) *
в переводе на С тот ассемблерный кусок выглядит приблизительно так:
Код
extern uint16_t crctable[32];
uint16_t crc;
void crc_update(uint8_t data) {
    crc ^= crctable[ data & 0x0F ];
    crc ^= crctable[ 16 + (data >>4) ];
}
Что-то не пахнет тут CRC.

Я бы ещё усилил, примерно так
Код
void crc_update(uint8_t data)
{
  crc ^= crctable2[ data ];
}

А это чисто табличный метод счёта crc. Возражения есть(:-)? В посте #18 я дал чисто идею реализации. Посчитал поточнее, получился 21 такт, включая загрузку байта из буфера и счётчик байт. В симуляторе вроде дышит, доберусь до работы, проверю поточнее. А может у кого под рукой есть тестовая строка с вычисленной crc?
ReAl
Цитата(=GM= @ Sep 20 2008, 02:17) *
Я бы ещё усилил, примерно так
Код
void crc_update(uint8_t data)
{
  crc ^= crctable2[ data ];
}
Кажется, речь шла о коде, который не вычисляет CRC с помощью таблички для 4 битов, а не при помощи таблички для 8 битов.
Хотя с 256-словной таблицей алгоритм не вычисляет CRC быстрее, спору нет.

Цитата(=GM= @ Sep 20 2008, 02:17) *
А это чисто табличный метод счёта crc. Возражения есть(:-)? В посте #18 я дал чисто идею реализации.
Да какие уж тут возражения... "слов нет"
=GM=
Цитата(ReAl @ Sep 20 2008, 06:16) *
Кажется, речь шла о коде, который не вычисляет CRC с помощью таблички для 4 битов, а не при помощи таблички для 8 битов.

Речь я вёл о том, что путём чисто математических преобразований можно показать идентичность вашего фрагмента в посте #21 моему фрагменту в посте #22.
ReAl
Цитата(=GM= @ Sep 20 2008, 20:36) *
Речь я вёл о том, что путём чисто математических преобразований можно показать идентичность вашего фрагмента в посте #21 моему фрагменту в посте #22.

Толку с того, если они оба не считают CRC ни при каких таблицах.
И для этого достаточно посмотреть даже на код из моего сообщения №21, ещё дальше можно не залазить.
Но если Вам так нравится этот Ваш фрагмент из сообщения №22, повторю
Цитата
он не считает CRC быстрее, чем код из сообщения №18 и его (подправленный) C-аналог из сообщения №21
=GM=
Цитата(ReAl @ Sep 19 2008, 19:27) *
Что-то не пахнет тут CRC. Мне кажется, что это ничем не лучше простой XORки всех байтов сообщения. Независимо от таблицы реакция на последовательность 0xA5 0xC3 будет та же, что и на 0xC3 0xA5 или на 0xC5 0xA3

А вот интересно, какая по-вашему будет crc для последовательностей
1) 0xA5, 0xC3
2) 0xC3, 0xA5
3) 0xC5, 0xA3 ?

Таблица для справки
Код
LOCAL_D int  crcbuf[256]=
{
0x0000,0x1021,0x2042,0x3063,0x4084,0x50A5,0x60C6,0x70E7,
0x8108,0x9129,0xA14A,0xB16B,0xC18C,0xD1AD,0xE1CE,0xF1EF,
0x1231,0x0210,0x3273,0x2252,0x52B5,0x4294,0x72F7,0x62D6,
0x9339,0x8318,0xB37B,0xA35A,0xD3BD,0xC39C,0xF3FF,0xE3DE,
0x2462,0x3443,0x0420,0x1401,0x64E6,0x74C7,0x44A4,0x5485,
0xA56A,0xB54B,0x8528,0x9509,0xE5EE,0xF5CF,0xC5AC,0xD58D,
0x3653,0x2672,0x1611,0x0630,0x76D7,0x66F6,0x5695,0x46B4,
0xB75B,0xA77A,0x9719,0x8738,0xF7DF,0xE7FE,0xD79D,0xC7BC,
0x48C4,0x58E5,0x6886,0x78A7,0x0840,0x1861,0x2802,0x3823,
0xC9CC,0xD9ED,0xE98E,0xF9AF,0x8948,0x9969,0xA90A,0xB92B,
0x5AF5,0x4AD4,0x7AB7,0x6A96,0x1A71,0x0A50,0x3A33,0x2A12,
0xDBFD,0xCBDC,0xFBBF,0xEB9E,0x9B79,0x8B58,0xBB3B,0xAB1A,
0x6CA6,0x7C87,0x4CE4,0x5CC5,0x2C22,0x3C03,0x0C60,0x1C41,
0xEDAE,0xFD8F,0xCDEC,0xDDCD,0xAD2A,0xBD0B,0x8D68,0x9D49,
0x7E97,0x6EB6,0x5ED5,0x4EF4,0x3E13,0x2E32,0x1E51,0x0E70,
0xFF9F,0xEFBE,0xDFDD,0xCFFC,0xBF1B,0xAF3A,0x9F59,0x8F78,
0x9188,0x81A9,0xB1CA,0xA1EB,0xD10C,0xC12D,0xF14E,0xE16F,
0x1080,0x00A1,0x30C2,0x20E3,0x5004,0x4025,0x7046,0x6067,
0x83B9,0x9398,0xA3FB,0xB3DA,0xC33D,0xD31C,0xE37F,0xF35E,
0x02B1,0x1290,0x22F3,0x32D2,0x4235,0x5214,0x6277,0x7256,
0xB5EA,0xA5CB,0x95A8,0x8589,0xF56E,0xE54F,0xD52C,0xC50D,
0x34E2,0x24C3,0x14A0,0x0481,0x7466,0x6447,0x5424,0x4405,
0xA7DB,0xB7FA,0x8799,0x97B8,0xE75F,0xF77E,0xC71D,0xD73C,
0x26D3,0x36F2,0x0691,0x16B0,0x6657,0x7676,0x4615,0x5634,
0xD94C,0xC96D,0xF90E,0xE92F,0x99C8,0x89E9,0xB98A,0xA9AB,
0x5844,0x4865,0x7806,0x6827,0x18C0,0x08E1,0x3882,0x28A3,
0xCB7D,0xDB5C,0xEB3F,0xFB1E,0x8BF9,0x9BD8,0xABBB,0xBB9A,
0x4A75,0x5A54,0x6A37,0x7A16,0x0AF1,0x1AD0,0x2AB3,0x3A92,
0xFD2E,0xED0F,0xDD6C,0xCD4D,0xBDAA,0xAD8B,0x9DE8,0x8DC9,
0x7C26,0x6C07,0x5C64,0x4C45,0x3CA2,0x2C83,0x1CE0,0x0CC1,
0xEF1F,0xFF3E,0xCF5D,0xDF7C,0xAF9B,0xBFBA,0x8FD9,0x9FF8,
0x6E17,0x7E36,0x4E55,0x5E74,0x2E93,0x3EB2,0x0ED1,0x1EF0
};
ReAl
Вы так упорствуете в своих заблуждениях...
Цитата(=GM= @ Sep 22 2008, 01:30) *
А вот интересно, какая по-вашему будет crc для последовательностей
1) 0xA5, 0xC3
2) 0xC3, 0xA5
3) 0xC5, 0xA3 ?
Ну я не факир-вычислитель, сразу так не скажу, но что она будет разной - уверен. Какой именно будет - сейчас сбацаем тест.

Цитата(=GM= @ Sep 22 2008, 01:30) *
Таблица для справки
Код
LOCAL_D int  crcbuf[256]=
{
0x0000,0x1021,0x2042,0x3063,0x4084,0x50A5,0x60C6,0x70E7,
...};
Таблица не для того полинома и направления сдвига, который обсуждается в теме, ну да ладно... Для основной темы это только частичный оффтопик, но тема веников наконец-то будет раскрыта.

CODE
#include <stdint.h>
#include <stdio.h>

uint16_t crctable[256]=
{
0x0000,0x1021,0x2042,0x3063,0x4084,0x50A5,0x60C6,0x70E7,
0x8108,0x9129,0xA14A,0xB16B,0xC18C,0xD1AD,0xE1CE,0xF1EF,
0x1231,0x0210,0x3273,0x2252,0x52B5,0x4294,0x72F7,0x62D6,
0x9339,0x8318,0xB37B,0xA35A,0xD3BD,0xC39C,0xF3FF,0xE3DE,
0x2462,0x3443,0x0420,0x1401,0x64E6,0x74C7,0x44A4,0x5485,
0xA56A,0xB54B,0x8528,0x9509,0xE5EE,0xF5CF,0xC5AC,0xD58D,
0x3653,0x2672,0x1611,0x0630,0x76D7,0x66F6,0x5695,0x46B4,
0xB75B,0xA77A,0x9719,0x8738,0xF7DF,0xE7FE,0xD79D,0xC7BC,
0x48C4,0x58E5,0x6886,0x78A7,0x0840,0x1861,0x2802,0x3823,
0xC9CC,0xD9ED,0xE98E,0xF9AF,0x8948,0x9969,0xA90A,0xB92B,
0x5AF5,0x4AD4,0x7AB7,0x6A96,0x1A71,0x0A50,0x3A33,0x2A12,
0xDBFD,0xCBDC,0xFBBF,0xEB9E,0x9B79,0x8B58,0xBB3B,0xAB1A,
0x6CA6,0x7C87,0x4CE4,0x5CC5,0x2C22,0x3C03,0x0C60,0x1C41,
0xEDAE,0xFD8F,0xCDEC,0xDDCD,0xAD2A,0xBD0B,0x8D68,0x9D49,
0x7E97,0x6EB6,0x5ED5,0x4EF4,0x3E13,0x2E32,0x1E51,0x0E70,
0xFF9F,0xEFBE,0xDFDD,0xCFFC,0xBF1B,0xAF3A,0x9F59,0x8F78,
0x9188,0x81A9,0xB1CA,0xA1EB,0xD10C,0xC12D,0xF14E,0xE16F,
0x1080,0x00A1,0x30C2,0x20E3,0x5004,0x4025,0x7046,0x6067,
0x83B9,0x9398,0xA3FB,0xB3DA,0xC33D,0xD31C,0xE37F,0xF35E,
0x02B1,0x1290,0x22F3,0x32D2,0x4235,0x5214,0x6277,0x7256,
0xB5EA,0xA5CB,0x95A8,0x8589,0xF56E,0xE54F,0xD52C,0xC50D,
0x34E2,0x24C3,0x14A0,0x0481,0x7466,0x6447,0x5424,0x4405,
0xA7DB,0xB7FA,0x8799,0x97B8,0xE75F,0xF77E,0xC71D,0xD73C,
0x26D3,0x36F2,0x0691,0x16B0,0x6657,0x7676,0x4615,0x5634,
0xD94C,0xC96D,0xF90E,0xE92F,0x99C8,0x89E9,0xB98A,0xA9AB,
0x5844,0x4865,0x7806,0x6827,0x18C0,0x08E1,0x3882,0x28A3,
0xCB7D,0xDB5C,0xEB3F,0xFB1E,0x8BF9,0x9BD8,0xABBB,0xBB9A,
0x4A75,0x5A54,0x6A37,0x7A16,0x0AF1,0x1AD0,0x2AB3,0x3A92,
0xFD2E,0xED0F,0xDD6C,0xCD4D,0xBDAA,0xAD8B,0x9DE8,0x8DC9,
0x7C26,0x6C07,0x5C64,0x4C45,0x3CA2,0x2C83,0x1CE0,0x0CC1,
0xEF1F,0xFF3E,0xCF5D,0xDF7C,0xAF9B,0xBFBA,0x8FD9,0x9FF8,
0x6E17,0x7E36,0x4E55,0x5E74,0x2E93,0x3EB2,0x0ED1,0x1EF0
};


uint16_t gm_crc_update(uint16_t crc, uint8_t data)
{
return crc ^ crctable[ data ];
}


uint16_t canonical_crc_update(uint16_t crc, uint8_t data)
{
crc = crc ^ ((uint16_t)data << 8);
for (int i=0; i<8; i++) {
if (crc & 0x8000)
crc = (crc << 1) ^ 0x1021;
else
crc <<= 1;
}

return crc;
}


uint16_t fast_table_crc_update(uint16_t crc, uint8_t data)
{
uint8_t index = (crc >> 8) ^ data;
crc = (crc << 8) ^ crctable[index];
return crc;
}


uint16_t fast_non_table_crc_update(uint16_t crc, uint8_t data)
{
uint8_t carry = (crc >> 8) ^ data;
carry = carry ^ (carry >> 4);
crc = (((crc ^ (carry << 4)) << 8) | carry) ^ (carry << 5);
return crc;
}


//-------------------------------------------------------------------------

uint16_t calc_crc( uint16_t (*crc_update)(uint16_t, uint8_t), const uint8_t *buf, int len)
{
uint16_t crc = 0;
while(len--)
crc = crc_update(crc, *buf++);
return crc;
}

void show_crc( uint16_t (*crc_update)(uint16_t, uint8_t), const uint8_t *buf, int len)
{
printf("crc = %04X for buf = ", calc_crc( crc_update, buf, len) );
for(int i = 0; i < len; ++i)
printf(" %2X", buf[i]);
putchar('\n');
}

const uint8_t buf1[] = { 0xA5, 0xC3 };
const uint8_t buf2[] = { 0xC3, 0xA5 };
const uint8_t buf3[] = { 0xC5, 0xA3 };


int main()
{
printf("\n=== GM super-fast table algorithm ==========\n");
show_crc( gm_crc_update, buf1, sizeof(buf1) );
show_crc( gm_crc_update, buf2, sizeof(buf2) );
show_crc( gm_crc_update, buf3, sizeof(buf3) );

printf("\n=== Canonical bitwise algorithm ============\n");
show_crc( canonical_crc_update, buf1, sizeof(buf1) );
show_crc( canonical_crc_update, buf2, sizeof(buf2) );
show_crc( canonical_crc_update, buf3, sizeof(buf3) );

printf("\n=== Correct table bytewise algorithm =======\n");
show_crc( fast_table_crc_update, buf1, sizeof(buf1) );
show_crc( fast_table_crc_update, buf2, sizeof(buf2) );
show_crc( fast_table_crc_update, buf3, sizeof(buf3) );

printf("\n=== Fast non-table bytewise algorithm =======\n");
show_crc( fast_non_table_crc_update, buf1, sizeof(buf1) );
show_crc( fast_non_table_crc_update, buf2, sizeof(buf2) );
show_crc( fast_non_table_crc_update, buf3, sizeof(buf3) );
}


Код
===  GM super-fast table algorithm ==========
crc = 0C60 for buf =  A5 C3
crc = 0C60 for buf =  C3 A5
crc = 0C60 for buf =  C5 A3

===  Canonical bitwise algorithm ============
crc = 0BA4 for buf =  A5 C3
crc = A648 for buf =  C3 A5
crc = 6C28 for buf =  C5 A3

===  Correct table bytewise algorithm =======
crc = 0BA4 for buf =  A5 C3
crc = A648 for buf =  C3 A5
crc = 6C28 for buf =  C5 A3

===  Fast non-table bytewise algorithm =======
crc = 0BA4 for buf =  A5 C3
crc = A648 for buf =  C3 A5
crc = 6C28 for buf =  C5 A3


Dixi
=GM=
Ну, я не упорствую, но вот это ваше "return crc^crctable[data]" как-то слишком прямолинейно, даже для меня. Это ж просто выборка из таблицы для вычисления цкс, а можно сделать выборку, используя тетрады данных, тогда размер таблицы уменьшится с 512 байт до 64 байт, надеюсь, вам это понятно. Собственно вот код, который так и делает. Как видите, веник метёт, и неплохо(:-).
Код
crcloop: ld      data,z+
         eor     data,crch
         mov     crch,crcl
         mov     yl,data
         andi    yl,0x0F
         ld      crcl,y
         ldd     tmp,y+16
         eor     crch,tmp
         eor     yl,data
         swap    yl
         ldd     tmp,y+32
         eor     crcl,tmp
         ldd     tmp,y+48
         eor     crch,tmp
         dec     datcnt
         brne    crcloop

Для ваших тестовых строк даёт те же цкс
Код
        .db   0xA5,0xC3      ;CRC=0x0BA4
        .db   0xC5,0xA3      ;CRC=0x6С28
        .db   0xC3,0xA5      ;CRC=0xA648

(Замечу в скобках, обычно цкс начинают считать с 0xFFFF, а не с 0х0000 как у вас)
singlskv
Цитата(=GM= @ Sep 23 2008, 15:51) *
(Замечу в скобках, обычно цкс начинают считать с 0xFFFF, а не с 0х0000 как у вас)
А вот и не всегда...

Modbus CRC-16 calculation
Polynomial: x^16 + x^15 + x^2 + 1 (0xa001)
Initial value: 0xffff

CRC-XMODEM calculation.
Polynomial: x^16 + x^12 + x^5 + 1 (0x1021)
Initial value: 0x0

CRC-CCITT calculation.
Polynomial: x^16 + x^12 + x^5 + 1 (0x8408)
Initial value: 0xffff

Dallas iButton 8-bit CRC calculation.
Polynomial: x^8 + x^5 + x^4 + 1 (0x8C)
Initial value: 0x0

Так что не всегда с FF(FF) начинают, зависит от применения,
хотя конечно для обсуждаемого здесь Modbus начинают всегда с 0xFFFF,
и начинать с 0 для передачи по UART плохо...
ReAl
Цитата(=GM= @ Sep 23 2008, 14:51) *
Ну, я не упорствую, но вот это ваше "return crc^crctable[data]" как-то слишком прямолинейно, даже для меня. Это ж просто выборка из таблицы для вычисления цкс, а можно сделать выборку, используя тетрады данных, тогда размер таблицы уменьшится с 512 байт до 64 байт, надеюсь, вам это понятно.
Понятно, именно потому при переводе Вашего ассемблерного кода на С я оставил потетрадное обращение.
Однако с каких это пор return crc^crctable[data] моё?
Я только ушёл от глобальной переменной, мне так удобнее было. И "это ж просто выборкой" оно стало, когда стало "моим", а пока было Ваше, оно было "чисто табличным методом вычисления CRC":
Цитата(=GM= @ Sep 20 2008, 02:17) *
Я бы ещё усилил, примерно так
Код
void crc_update(uint8_t data)
{
  crc ^= crctable2[ data ];
}
А это чисто табличный метод счёта crc. Возражения есть(:-)?
Ну так что, оно таки считает CRC ???
Вы сами сказали, что оно "эквивалентно" тому, что я написал в качестве аналога Вашего ассемблерного кода.
Не этого, конечно:
Цитата(=GM= @ Sep 23 2008, 14:51) *
Код
crcloop: ld      data,z+
         eor     data,crch
         mov     crch,crcl
         mov     yl,data
         andi    yl,0x0F
         ld      crcl,y
         ldd     tmp,y+16
         eor     crch,tmp
         eor     yl,data
         swap    yl
         ldd     tmp,y+32
         eor     crcl,tmp
         ldd     tmp,y+48
         eor     crch,tmp
         dec     datcnt
         brne    crcloop

А этого:
Цитата(=GM= @ Sep 19 2008, 15:07) *
Идея простая, как веник. Ну вот как-то так, таблица должна быть настроена на границу 256 байт, можно и 64.
Код
        lxi     yh,high(crctable)
        mov     yl,data
        ani     yl,0x0F
        ld      tmp,y+
        eor     crcl,tmp
        ld      tmp,y+
        eor     crch,tmp
        mov     yl,data
        swap    yl
        ani     yl,0x0F
        sbci    yl,-32
        ld      tmp,y+
        eor     crcl,tmp
        ld      tmp,y+
        eor     crch,tmp
Который таки считает CRC или нет? Честно говоря, на моё замечание об одинаковости результата для разных двухбайтовых последовательностей я ожидал от Вас чего-то в духе "ой, если так, то я где-то на ночь глядя ошибся, сейчас гляну". Вместо этого Вы поинтересовались моим мнением "так какая же CRC должна быть" и предложили табличку.
Если бы с самого начала код хоть немного был похож на тот, который теперь, я бы ни слова не сказал. А так - для демонстрации считывания из двух маленьких таблиц тут слишком много лишнего, для демонстрации вычисления CRC слишком многого недостаёт.
(Ну, возможно повторил бы мысль Rst7, что ОЗУ чаще жалко, чем код, и даже сотношение 64ОЗУ : 512флеша лучше в сторону процентов от флеша, чем оно обычно у AVR. В случае же LPM вместо LDD добавится эдак 6-8 тактов и выиграша по скорости у "быстрого бестабличного" метода не будет)

Цитата(=GM= @ Sep 23 2008, 14:51) *
Для ваших тестовых строк даёт те же цкс
Код
        .db   0xA5,0xC3      ;CRC=0x0BA4
        .db   0xC5,0xA3      ;CRC=0xA648
        .db   0xC3,0xA5      ;CRC=0x6C28
Теперь даёт.

Цитата(=GM= @ Sep 23 2008, 14:51) *
(Замечу в скобках, обычно цкс начинают считать с 0xFFFF)
Разные CRC принято считать начиная с разного инициализатора. Полином из Вашей таблички применяется в протоколе XMODEM_CRC с инициализацией нулями, о чём уже сказано.
Была бы предложена другая табличка, я бы глянул, что принято для того полинома для сравнимости результатов.


Цитата(singlskv @ Sep 23 2008, 21:48) *
Так что не всегда с FF(FF) начинают, зависит от применения, хотя конечно для обсуждаемого здесь Modbus начинают всегда с 0xFFFF,
и начинать с 0 для передачи по UART плохо...
Для XMODEM, на мой взгляд, всё равно.
Пакет начинается с ненулевого спецсимвола и после него в аккумуляторе CRC будет уже не ноль, проблема неразличения последовательностей нулей разной длины в начале пакета пропадает.
А до него и пакет не начался.
=GM=
Цитата(ReAl @ Sep 23 2008, 20:33) *
Вы сами сказали, что оно "эквивалентно" тому, что я написал в качестве аналога Вашего ассемблерного кода

Ну, я рад, что все высказали всё, что хотели(:-). Кое-чему и поучиться можно было. Со своей стороны отмечу только, что до поста #16 у меня даже понятия не было о реализации подобным образом, идея возникла спонтанно. И самым сложным для меня было показать, каким образом получить цкс из укороченной таблицы, конечно, надо было обозвать операцию типа crclut, а не crc, что привело к ошибочному пониманию метода. Была ещё одна идея, как посчитать цкс без таблиц, занимает от 16 до 32 тактов, в среднем 24, ну это уже неинтересно.
defunct
Цитата(=GM= @ Sep 23 2008, 14:51) *
Для ваших тестовых строк даёт те же цкс
Код
        .db   0xA5,0xC3     ;CRC=0x0BA4
        .db   0xC5,0xA3     ;CRC=0x6С28
        .db   0xC3,0xA5     ;CRC=0xA648

(Замечу в скобках, обычно цкс начинают считать с 0xFFFF, а не с 0х0000 как у вас)

У вас ошибка CRC. Модбас работать не будет.
Д.б. сл. значения

.db 0xA5,0xC3 ;CRC=0xB13A
.db 0xC5,0xA3 ;CRC=0x9912
.db 0xC3,0xA5 ;CRC=0x3B91
=GM=
Цитата(defunct @ Sep 29 2008, 18:50) *
У вас ошибка CRC. Модбас работать не будет.

А вы это ReAl'у скажите, у меня с ним совпало. Что касается ошибки, посмотрите, правильную ли вы используете таблицу для получения коротких таблиц.
defunct
Цитата(=GM= @ Sep 30 2008, 12:26) *
А вы это ReAl'у скажите, у меня с ним совпало. Что касается ошибки, посмотрите, правильную ли вы используете таблицу для получения коротких таблиц.

Я не табличкой считал, а старым дедовским способом, описанным в спецификации на modbus (гарантированно правильным). ReAl от Вас хотел только неравенства всех трех значений, а нужно чтоб кроме неравенства еще и считалось _то_ что надо.
=GM=
Цитата(defunct @ Sep 30 2008, 12:13) *
Я не табличкой считал, а старым дедовским способом, описанным в спецификации на modbus (гарантированно правильным). ReAl от Вас хотел только неравенства всех трех значений, а нужно чтоб кроме неравенства еще и считалось _то_ что надо

ReAl считал тремя способами, между прочим, и старым дедовским тоже. И вообще-то, ReAl хотел добиться правды, он её добился, все получили более-менее быстрый алгоритм с таблицей в ВОСЕМЬ раз меньше стандартной таблицы. А я считал по своему алгоритму, и результаты всех четырёх расчётов совпали, вам что, этого мало?

Я ж вам сказал, возьмите правильную, нужную таблицу для модбаса, и дело в шляпе. Алгоритм универсальный. А укороченные таблицы вычленяются из штатных.
Сергей Борщ
Цитата(=GM= @ Sep 30 2008, 18:35) *
Я ж вам сказал, возьмите правильную, нужную таблицу для модбаса, и дело в шляпе.
Но ведь это вы "вбросили" сюда неправильную неподходящую таблицу, почему исправлять это недоразумение должны ReAl и defunct? Начали - доводите до конца. И озвучьте характеристики вашего кода (размер/такты), чтобы можно было сравнить.
defunct
Цитата(=GM= @ Sep 30 2008, 18:35) *
Я ж вам сказал, возьмите правильную, нужную таблицу для модбаса, и дело в шляпе. Алгоритм универсальный. А укороченные таблицы вычленяются из штатных.

Проблема в том, что не получилось у меня создать таблицу для модбаса (256x16) чтобы работала по вашему алгоритму:
Код
void crc_update(uint8_t data)
{
  crc ^= crctable2[ data ];
}


Хотите верьте, хотите проверьте.. Не работает, неверные результаты дает на уже втором символе.
А просто от фонаря взять какой-то полином, слегка изменить алгоритм и сказать, что это тоже CRC это все могут.
=GM=
Цитата(Сергей Борщ @ Sep 30 2008, 15:04) *
Но ведь это вы "вбросили" сюда неправильную неподходящую таблицу, почему исправлять это недоразумение должны ReAl и defunct? Начали - доводите до конца. И озвучьте характеристики вашего кода (размер/такты), чтобы можно было сравнить.

Во, Сергей Борщ объявился, а мы уж и не ждали (:-)! Это ведь вы заварили всю кашу, как, да что, да опубликуйте код...

ReAl ничего не исправлял и не должен, с какой стати? Он посчитал цкс по моей таблице, она совпала с моим расчётом, он и согласился со мной.

Теперь о таблице. Если есть таблица для любого полинома, то можно легко получить укороченную таблицу, что я и пытался показать своим первым кодом (по крайней мере, для меня было трудно написать с ходу), но тут все накинулись, не так, не работает, да и озу, ой как жалко, целых 64 байта...
Короче, дайте таблицу для модбаса, я её подрихтую.

Как я уже говорил, таблица занимает 64 байта озу, программа считает цкс для одного байта в цикле за 22 МЦ, хотя можно уменьшить до 21.

Цитата(defunct @ Sep 30 2008, 15:46) *
Проблема в том, что не получилось у меня создать таблицу для модбаса (256x16) чтобы работала по вашему алгоритму: crc ^= crctable2[ data ]

defunct, это не алгоритм, мой алгоритм приведен в посте #28. А то, что вы привели, мне бы надо переписать как-то так CRCLUT=crctable2[ data ], выше я уже говорил про это. Вот вам код для расчёта по таблице, случайно оказался дома на одном из двд, 100% рабочий, вырвал из моей старой программы 2001 года, работала с очень неудобным таг-ридером, который проектировал один кетаец в юса, отсюда такая таблица
Код
lcrc=hcrc=0x00FF;
for(i=0;i<nbyte;i++)
{
  indx=(datain[i]^hcrc)&0x00FF;
  crc=crcbuf[indx];
  hcrc=((crc>>8)^lcrc)&0xFF;
  lcrc=crc&0x00FF;
}
hcrc=hcrc^0x00FF; lcrc=lcrc^0x00FF;
=GM=
Цитата(defunct @ Sep 29 2008, 18:50) *
Mодбас работать не будет. Д.б. сл. значения
.db 0xA5,0xC3 ;CRC=0xB13A
.db 0xC5,0xA3 ;CRC=0x9912
.db 0xC3,0xA5 ;CRC=0x3B91

defuct, вы уверены, что будут такие цкс для этих строк, если начальное значение цкс равно 0хFFFF?
defunct
Цитата(=GM= @ Oct 2 2008, 18:27) *
defuct, вы уверены, что будут такие цкс для этих строк, если начальное значение цкс равно 0хFFFF?

Выше приводили рабочие версии расчета CRC. Вот еще одна рабочая реализация:

Код
#define CRC16_DIVISOR (0xA001)
#define CRC16_ERROR (0xFFFF)

/***********************************************
* ---> pData - msg to be protected             *
* ---> size - msg size in bytes                *
* <--- crc16                                   *
***********************************************/
U16 CRC16(U8 *pData, U8 size)
{
    
    U16 retval   = CRC16_ERROR;
    U8  i, tmp;
    
    if (!size)
       return retval;

    do
    {
        retval ^= *pData++;
        for( i = 0; i < 8; i++)
        {
            tmp = retval & 0x01;
            retval >>= 1;
            if (tmp)
            {
                retval ^= CRC16_DIVISOR;
            }
        }
    } while (--size);
    return retval;
}

Если не сложно подставьте с теми данными.
=GM=
Цитата(defunct @ Oct 2 2008, 15:37) *
Если не сложно подставьте с теми данными

Подставлю, конечно, но попозже, на работе запарка в кои-то веки, и не выходит у меня сингулярное разложение матриц, а я хочу взять отпуск на недельку. Ну ладно, не будем о грустном, я почему спрашиваю, нашёл полное описание модбаса, там была таблица, я по ней посчитал вручную, и ответ не сошёлся с вашим. Да, там ещё и алгоритм приведен, как считать, практически один в один с моим по сути, могу показать, если надо, вместе с таблицей.
=GM=
Проверил ещё раз, получаются другие результаты, но пригляделся - они такие же, только у вас переставлены младший и старший байты. Должно быть так

.db 0xA5,0xC3 ;CRC=0x3AB1
.db 0xC5,0xA3 ;CRC=0x1299
.db 0xC3,0xA5 ;CRC=0x913B
CODE

the splitted crc table for the modbus protocol

/*Table of CRC values for high–order byte*/
static unsigned char auchCRCHi[]=
{
0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40
0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41
0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41
0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40
0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41
0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40
0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40
0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41
0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41
0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40
0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40
0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41
0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40
0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41
0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41
0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40
};
/*Table of CRC values for low–order byte*/
static char auchCRCLo[]=
{
0x00,0xC0,0xC1,0x01,0xC3,0x03,0x02,0xC2,0xC6,0x06,0x07,0xC7,0x05,0xC5,0xC4,0x04
0xCC,0x0C,0x0D,0xCD,0x0F,0xCF,0xCE,0x0E,0x0A,0xCA,0xCB,0x0B,0xC9,0x09,0x08,0xC8
0xD8,0x18,0x19,0xD9,0x1B,0xDB,0xDA,0x1A,0x1E,0xDE,0xDF,0x1F,0xDD,0x1D,0x1C,0xDC
0x14,0xD4,0xD5,0x15,0xD7,0x17,0x16,0xD6,0xD2,0x12,0x13,0xD3,0x11,0xD1,0xD0,0x10
0xF0,0x30,0x31,0xF1,0x33,0xF3,0xF2,0x32,0x36,0xF6,0xF7,0x37,0xF5,0x35,0x34,0xF4
0x3C,0xFC,0xFD,0x3D,0xFF,0x3F,0x3E,0xFE,0xFA,0x3A,0x3B,0xFB,0x39,0xF9,0xF8,0x38
0x28,0xE8,0xE9,0x29,0xEB,0x2B,0x2A,0xEA,0xEE,0x2E,0x2F,0xEF,0x2D,0xED,0xEC,0x2C
0xE4,0x24,0x25,0xE5,0x27,0xE7,0xE6,0x26,0x22,0xE2,0xE3,0x23,0xE1,0x21,0x20,0xE0
0xA0,0x60,0x61,0xA1,0x63,0xA3,0xA2,0x62,0x66,0xA6,0xA7,0x67,0xA5,0x65,0x64,0xA4
0x6C,0xAC,0xAD,0x6D,0xAF,0x6F,0x6E,0xAE,0xAA,0x6A,0x6B,0xAB,0x69,0xA9,0xA8,0x68
0x78,0xB8,0xB9,0x79,0xBB,0x7B,0x7A,0xBA,0xBE,0x7E,0x7F,0xBF,0x7D,0xBD,0xBC,0x7C
0xB4,0x74,0x75,0xB5,0x77,0xB7,0xB6,0x76,0x72,0xB2,0xB3,0x73,0xB1,0x71,0x70,0xB0
0x50,0x90,0x91,0x51,0x93,0x53,0x52,0x92,0x96,0x56,0x57,0x97,0x55,0x95,0x94,0x54
0x9C,0x5C,0x5D,0x9D,0x5F,0x9F,0x9E,0x5E,0x5A,0x9A,0x9B,0x5B,0x99,0x59,0x58,0x98
0x88,0x48,0x49,0x89,0x4B,0x8B,0x8A,0x4A,0x4E,0x8E,0x8F,0x4F,0x8D,0x4D,0x4C,0x8C
0x44,0x84,0x85,0x45,0x87,0x47,0x46,0x86,0x82,0x42,0x43,0x83,0x41,0x81,0x80,0x40
};

И программа, по которой считается цкс
Код
/* CRC Generation Function */
unsigned short CRC16(puchMsg, usDataLen)
unsigned char *puchMsg; /* message to calculate CRC upon */
unsigned short usDataLen; /* quantity of bytes in message */
{
unsigned char uchCRCHi = 0xFF; /* high byte of CRC initialized */
unsigned char uchCRCLo = 0xFF; /* low byte of CRC initialized */
unsigned uIndex; /* will index into CRC lookup table */
while (usDataLen––) /* pass through message buffer */
{
uIndex = uchCRCHi ^ *puchMsgg++; /* calculate the CRC */
uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex};
uchCRCLo = auchCRCLo[uIndex];
}
return (uchCRCHi << 8 | uchCRCLo);
}

И таблица, и программа взяты из описания протокола модбас
Сергей Борщ
Цитата(=GM= @ Oct 1 2008, 00:49) *
Во, Сергей Борщ объявился, а мы уж и не ждали (:-)! Это ведь вы заварили всю кашу, как, да что, да опубликуйте код...
Я и не пропадал. Внимательно смотрю, как вы выкручиваетесь. Не надо перекладывать с одной головы на здоровую. Кашу заварили вы:
Цитата(=GM= @ Sep 19 2008, 14:20) *
А что если сделать две таблицы на биты <7-4> и <3-0>? Прикинул программулю, получается 20 тактов на байт, размер 47 слов вместе с таблицами.
Я лишь попросил вас привести подтверждение этих цифр в виде кода. Алгоритм, который вы, снова не удосужившисть проверить, привели, оказался абсолютно нерабочим, что вам с цифрами в руках доказал ReAl. Вы довели его до ума, но от исходных "20 тактов на байт, размер 47 слов вместе с таблицами" не осталось и следа. И вдруг выясняется, что ваш алгоритм считает совсем другую CRC. И снова виноват кто-то другой:
Цитата(=GM= @ Sep 30 2008, 12:26) *
А вы это ReAl'у скажите, у меня с ним совпало.
Хотя:
Цитата(ReAl @ Sep 22 2008, 14:09) *
Таблица не для того полинома и направления сдвига, который обсуждается в теме, ну да ладно... Для основной темы это только частичный оффтопик, но тема веников наконец-то будет раскрыта.
"Казалось бы, при чем здесь Лужков"?

Цитата(=GM= @ Oct 1 2008, 00:49) *
ReAl ничего не исправлял и не должен, с какой стати? Он посчитал цкс по моей таблице, она совпала с моим расчётом, он и согласился со мной.
Совесть имейте:
Цитата(ReAl @ Sep 19 2008, 23:27) *
Что-то не пахнет тут CRC.
Мне кажется, что это ничем не лучше простой XORки всех байтов сообщения.
Независимо от таблицы реакция на последовательность 0xA5 0xC3 будет та же, что и на
0xC3 0xA5 или на 0xC5 0xA3.


Итог после всех ваших исправлений "прикинутой программули": уменьшить таблицы можно, но "47 слов вместе с таблицами и 20 тактов", которые были главными козырями, нету и близко. Хотя виноваты, конечно, все, кроме =GM=.
Цитата(=GM= @ Oct 3 2008, 11:30) *
Проверил ещё раз, получаются другие результаты, но пригляделся - они такие же, только у вас переставлены младший и старший байты.
Честно-честно? Проверили? И что, это работало?
Цитата
Код
uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex};
Цитата(=GM= @ Oct 3 2008, 11:30) *
И таблица, и программа взяты из описания протокола модбас
Ну а где же ваши чудо-таблицы? Что же вы проверяли - описание модбас?
=GM=
Вот укороченная таблица для модбаса
Код
;a short crc table for modbus protocol
        .db   0x00,0xC0,0xC1,0x01,0xC3,0x03,0x02,0xC2
        .db   0xC6,0x06,0x07,0xC7,0x05,0xC5,0xC4,0x04
        .db   0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41
        .db   0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40
        .db   0x00,0xCC,0xD8,0x14,0xF0,0x3C,0x28,0xE4
        .db   0xA0,0x6C,0x78,0xB4,0x50,0x9C,0x88,0x44
        .db   0x00,0x01,0x01,0x00,0x01,0x00,0x00,0x01
        .db   0x01,0x00,0x00,0x01,0x00,0x01,0x01,0x00

Вот тестовые строки и цкс для каждой строки, как результат работы моей программы с укороченной таблицей. А у Юры явно перепутаны старший и младший байты
Код
    .db   0xA5,0xC3;CRC=0x3AB1
    .db   0xC5,0xA3;CRC=0x1299
    .db   0xC3,0xA5;CRC=0x913B

Как видите, таблица занимает 32 слова в озу, сама программа занимает 16 слов во флеше, в итоге получается 48. Один байт из буфера обсчитывается за 22 такта с учетом организации цикла. Можно сделать за 21 такт, не стал приводить, поскольку сложнее для понимания. Кому надо, легко сделает. Можно также развернуть цикл, будет примерно 96флеш+32озу=128 слов, время выполнения будет 19 тактов на байт.

Ещё раз, алгоритм универсальный, работает для ЛЮБОГО полинома, поскольку берёт данные из укороченной таблицы соответствующего полинома.

На остальное не отвечаю по понятной причине.
defunct
Цитата(=GM= @ Oct 3 2008, 14:43) *
Вот тестовые строки и цкс для каждой строки, как результат работы моей программы с укороченной таблицей. А у Юры явно перепутаны старший и младший байты

=GM=, наткнулся на табличную реализацию - точно такую же как Вы привели wink.gif
У меня все-таки ничего не перепутано, похоже, что пример который Вы нашли сделан для big-endian проца.
Для little-endian результат должен быть такой как у меня. Ну и чтоб с приведеными табличками можно было работать на AVR привожу код
(для AVR'а надо пользовать little-endian функцию):

Код
#if (BIG_ENDIAN)
// big endian CPU
U16 crc16(U16 crc, U8 data)
{
    U8 index;
    U8 crc_High = crc >> 8;
    U8 crc_Low = crc & 0xFF;

    index = crc_High ^ data;
    crc_High = crc_Low ^ mb_table_level1[ index ];
    crc_Low = mb_table_level2[ index ];

    return (crc_High << 8) | crc_Low;
}
#else
// little endian CPU
U16 CRC16_Update(U16 crc, U8 data)
{
    U8 index;
    U8 crc_High = crc >> 8;
    U8 crc_Low  = crc & 0xFF;

    index = crc_Low ^ data;
    crc_Low = crc_High ^ mb_table_level1[ index ];
    crc_High = mb_table_level2[ index ];

    return (crc_High << 8) | crc_Low;
}
#endif


mb_table_level1 ---> auchCRCHi из поста #42
mb_table_level2 ---> auchCRCLo из поста #42
=GM=
Цитата(defunct @ Jan 9 2009, 00:07) *
=GM=, наткнулся на табличную реализацию - точно такую же как Вы привели wink.gif У меня все-таки ничего не перепутано, похоже, что пример который Вы нашли сделан для big-endian проца

Вполне может быть, они такие путаники. Вот выжимка из описания модбаса как вычисляется кцс
CODE

Алгоритм генерации CRC:
1. 16-ти битовый регистр загружается числом FF hex (все 1), и используется далее как регистр CRC.
2. Первый байт сообщения складывается по ИСКЛЮЧАЮЩЕМУ ИЛИ с содержимым регистра CRC. Результат помещается в регистр CRC.
3. Регистр CRC сдвигается вправо(в направлении младшего бита) на 1 бит, старший бит заполняется 0.
4. (Если младший бит 0): Повторяется шаг 3 (сдвиг)
(Если младший бит 1): Делается операция ИСКЛЮЧАЮЩЕЕ ИЛИ регистра CRC и полиномиального числа A001 hex.
5. Шаги 3 и 4 повторяются восемь раз.
6. Повторяются шаги со 2 по 5 для следующего сообщения. Это повторяется до тех пор пока все байты сообщения не будут обработаны.
7. Финальное содержание регистра CRC и есть контрольная сумма.
РАЗМЕЩЕНИЕ CRC В СООБЩЕНИИ
При передаче 16 бит контрольной суммы CRC в сообщении, сначала передается младший байт, затем старший. Например, если CRC равна 1241 hex :

| Адрес | Функция | Счетчик | Данные | CRC Ст | CRC Мл |
41 12

ПРИМЕР
Пример функции на языке C реализующей генерацию CRC приведен ниже. Все возможные величины CRC загружены в два массива.
Один массив содержит все 256 возможных комбинаций CRC для старшего байта поля CRC, другой массив содержит данные для младшего байта.
Идексация CRC в этом случая обеспечивает быстрое выполнение вычислений новой величины CRC для каждого нового байта из буфера сообщения.
Функция принимает два аргумента:
unsigned char *puchMsg; /* Указатель на буфер */
unsigned short usDataLen; /* Количество байтов в буфере */
Функция возвращает CRC как тип unsigned short.
static unsigned char auchCRCHi [ ] =
{
0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,
0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,
0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,
0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,
0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,
0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,
0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,
0x22,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,
0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,
0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,
0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,
0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,
0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,
0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,
0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,
0x40,0x01,0xC0,0x80,0x41,0x00,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,
0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,
0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,
0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,
0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40
}
static char auchCRCLo [ ] =
{
0x00,0xC0,0xC1,0x01,0xC3,0x03,0x02,0xC2,0xC6,0x06,0x07,0xC7,0x05,
0xC5,0xC4,0x04,0xCC,0x0C,0x0D,0xCD,0x0F,0xCF,0xCE,0x0E,0x0A,0xCA,
0xCB,0x0B,0xC9,0x09,0x08,0xC8,0xD8,0x18,0x19,0xD9,0x1B,0xDB,0xDA,
0x1A,0x1E,0xDE,0xDF,0x1F,0xDD,0x1D,0x1C,0xDC,0x14,0xD4,0xD5,0x15,
0xD7,0x17,0x16,0xD6,0xD2,0x12,0x13,0xD3,0x11,0xD1,0xD0,0x10,0xF0,
0x30,0x31,0xF1,0x33,0xF3,0xF2,0x32,0x36,0xF6,0xF7,0x37,0xF5,0x35,
0x34,0xF4,0x3C,0xFC,0xFD,0x3D,0xFF,0x3F,0x3E,0xFE,0xFA,0x3A,0x3B,
0xFB,0x39,0xF9,0xF8,0x38,0x28,0xE8,0xE9,0x29,0xEB,0x2B,0x2A,0xEA,
0xEE,0x2E,0x2F,0xEF,0x2D,0xED,0xEC,0x2C,0xE4,0x24,0x25,0xE5,0x27,
0xE7,0xE6,0x26,0x22,0xE2,0xE3,0x23,0xE1,0x21,0x20,0xE0,0xA0,0x60,
0x61,0xA1,0x63,0xA3,0xA2,0x62,0x66,0xA6,0xA7,0x67,0xA5,0x65,0x64,
0xA4,0x6C,0xAC,0xAD,0x6D,0xAF,0x6F,0x6E,0xAE,0xAA,0x6A,0x6B,0xAB,
0x69,0xA9,0xA8,0x68,0x78,0xB8,0xB9,0x79,0xBB,0x7B,0x7A,0xBA,0xBE,
0x7E,0x7F,0xBF,0x7D,0xBD,0xBC,0x7C,0xB4,0x74,0x75,0xB5,0x77,0xB7,
0xB6,0x76,0x72,0xB2,0xB3,0x73,0xB1,0x71,0x70,0xB0,0x50,0x90,0x91,
0x51,0x93,0x53,0x52,0x92,0x96,0x56,0x57,0x97,0x55,0x95,0x94,0x54,
0x9C,0x5C,0x5D,0x9D,0x5F,0x9F,0x9E,0x5E,0x5A,0x9A,0x9B,0x5B,0x99,
0x59,0x58,0x98,0x88,0x48,0x49,0x89,0x4B,0x8B,0x8A,0x4A,0x4E,0x8E,
0x8F,0x4F,0x8D,0x4D,0x4C,0x8C,0x44,0x84,0x85,0x45,0x87,0x47,0x46,
0x86,0x82,0x42,0x43,0x83,0x41,0x81,0x80,0x40
}
unsigned short CRC16(puchMsg, usDataLen)
unsigned char *puchMsg;
unsigned short usDataLen;
{
unsigned char uchCRCHi = 0xFF;
unsigned char uchCRCLo = 0xFF;
while (usDataLen--)
{
uIndex = uchCRCHi
*puchMsg++;
uchCRCHi = uchCRCLo
auchCRCHi [ uIndex ];
uchCRCLo = auchCRCLo [ uIndex ];
}
return (uchCRCHi << 8 | uchCRCLo);
}

Обратите внимание, в примере CRC=0х1241, прямо указано сначала передается младший байт, затем старший, а потом в табличке почему-то стоит CRC Ст над младшим байтом. Почему стоит Ст, а не Мл? Ну не п&#o$a$ы, а?
dred73
Держи рабочие функции по расчету CRC8 и CRC16
Код
void CRC8(unsigned char in,unsigned char *crc)
{
#define POLYNOM_8 0x18 // полином для 8 битной КС
  for (unsigned char i=0; i<8; i++)
  {
    if ((in ^ *crc) & 0x01)
    {
      *crc = ((*crc ^ POLYNOM_8)>>1)|0x80;
      in = (in>>1)|0x80;
    }
    else
    {
      in >>= 1;
      *crc >>= 1;
    }
  }
}


void CRC16(unsigned char in, unsigned int *crc)
{
#define POLYNOM_16 0xA001 // полином для 16 битной КС
  *crc ^= (int)in;
  for (unsigned char i=0; i<8; i++)
  {
    *crc = (*crc & 0x01)? (*crc >> 1) ^ POLYNOM_16 : (*crc >> 1);
  }
}
Kovrov
Ребят, может кто нибудь оканчательный исходник выложить табличного CRC16 на ASMе?
=GM=
Код в посте #28, таблица для модбаса и тестовые векторы в посте #44.
Kovrov
В этом примере таблица находится в рам пусть и 64 байта
у меня диффицит озу.
Есть пример таблицы во флеш с полиномом под модбас?
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.