2013-10-13 104 views
8

Sto implementando un software in cui leggo e scrivo i dati nel protocolo Modbus RTU via seriale. Per questo, ho bisogno di calcolare i due byte CRC alla fine della stringa di byte, ma non sono in grado di farlo.Calcolo di Modbus RTU CRC 16

Ricerca in tutto il web, ho trovato due funzioni che sembra per il calcolo del CRC correttamente:

WORD CRC16 (const BYTE *nData, WORD wLength) 
{ 
    static const WORD wCRCTable[] = { 
     0X0000, 0XC0C1, 0XC181, 0X0140, 0XC301, 0X03C0, 0X0280, 0XC241, 
     0XC601, 0X06C0, 0X0780, 0XC741, 0X0500, 0XC5C1, 0XC481, 0X0440, 
     0XCC01, 0X0CC0, 0X0D80, 0XCD41, 0X0F00, 0XCFC1, 0XCE81, 0X0E40, 
     0X0A00, 0XCAC1, 0XCB81, 0X0B40, 0XC901, 0X09C0, 0X0880, 0XC841, 
     0XD801, 0X18C0, 0X1980, 0XD941, 0X1B00, 0XDBC1, 0XDA81, 0X1A40, 
     0X1E00, 0XDEC1, 0XDF81, 0X1F40, 0XDD01, 0X1DC0, 0X1C80, 0XDC41, 
     0X1400, 0XD4C1, 0XD581, 0X1540, 0XD701, 0X17C0, 0X1680, 0XD641, 
     0XD201, 0X12C0, 0X1380, 0XD341, 0X1100, 0XD1C1, 0XD081, 0X1040, 
     0XF001, 0X30C0, 0X3180, 0XF141, 0X3300, 0XF3C1, 0XF281, 0X3240, 
     0X3600, 0XF6C1, 0XF781, 0X3740, 0XF501, 0X35C0, 0X3480, 0XF441, 
     0X3C00, 0XFCC1, 0XFD81, 0X3D40, 0XFF01, 0X3FC0, 0X3E80, 0XFE41, 
     0XFA01, 0X3AC0, 0X3B80, 0XFB41, 0X3900, 0XF9C1, 0XF881, 0X3840, 
     0X2800, 0XE8C1, 0XE981, 0X2940, 0XEB01, 0X2BC0, 0X2A80, 0XEA41, 
     0XEE01, 0X2EC0, 0X2F80, 0XEF41, 0X2D00, 0XEDC1, 0XEC81, 0X2C40, 
     0XE401, 0X24C0, 0X2580, 0XE541, 0X2700, 0XE7C1, 0XE681, 0X2640, 
     0X2200, 0XE2C1, 0XE381, 0X2340, 0XE101, 0X21C0, 0X2080, 0XE041, 
     0XA001, 0X60C0, 0X6180, 0XA141, 0X6300, 0XA3C1, 0XA281, 0X6240, 
     0X6600, 0XA6C1, 0XA781, 0X6740, 0XA501, 0X65C0, 0X6480, 0XA441, 
     0X6C00, 0XACC1, 0XAD81, 0X6D40, 0XAF01, 0X6FC0, 0X6E80, 0XAE41, 
     0XAA01, 0X6AC0, 0X6B80, 0XAB41, 0X6900, 0XA9C1, 0XA881, 0X6840, 
     0X7800, 0XB8C1, 0XB981, 0X7940, 0XBB01, 0X7BC0, 0X7A80, 0XBA41, 
     0XBE01, 0X7EC0, 0X7F80, 0XBF41, 0X7D00, 0XBDC1, 0XBC81, 0X7C40, 
     0XB401, 0X74C0, 0X7580, 0XB541, 0X7700, 0XB7C1, 0XB681, 0X7640, 
     0X7200, 0XB2C1, 0XB381, 0X7340, 0XB101, 0X71C0, 0X7080, 0XB041, 
     0X5000, 0X90C1, 0X9181, 0X5140, 0X9301, 0X53C0, 0X5280, 0X9241, 
     0X9601, 0X56C0, 0X5780, 0X9741, 0X5500, 0X95C1, 0X9481, 0X5440, 
     0X9C01, 0X5CC0, 0X5D80, 0X9D41, 0X5F00, 0X9FC1, 0X9E81, 0X5E40, 
     0X5A00, 0X9AC1, 0X9B81, 0X5B40, 0X9901, 0X59C0, 0X5880, 0X9841, 
     0X8801, 0X48C0, 0X4980, 0X8941, 0X4B00, 0X8BC1, 0X8A81, 0X4A40, 
     0X4E00, 0X8EC1, 0X8F81, 0X4F40, 0X8D01, 0X4DC0, 0X4C80, 0X8C41, 
     0X4400, 0X84C1, 0X8581, 0X4540, 0X8701, 0X47C0, 0X4680, 0X8641, 
     0X8201, 0X42C0, 0X4380, 0X8341, 0X4100, 0X81C1, 0X8081, 0X4040 }; 

    BYTE nTemp; 
    WORD wCRCWord = 0xFFFF; 

    while (wLength--) 
    { 
     nTemp = *nData++^wCRCWord; 
     wCRCWord >>= 8; 
     wCRCWord ^= wCRCTable[nTemp]; 
    } 
    return wCRCWord; 
} // End: CRC16 

E

uint CRC16_2(QByteArray buf, int len) 
{ 
    uint crc = 0xFFFF; 

    for (int pos = 0; pos < len; pos++) 
    { 
    crc ^= (uint)buf[pos];   // XOR byte into least sig. byte of crc 

    for (int i = 8; i != 0; i--) { // Loop over each bit 
     if ((crc & 0x0001) != 0) {  // If the LSB is set 
     crc >>= 1;     // Shift right and XOR 0xA001 
     crc ^= 0xA001; 
     } 
     else       // Else LSB is not set 
     crc >>= 1;     // Just shift right 
    } 
    } 
    // Note, this number has low and high bytes swapped, so use it accordingly (or swap bytes) 
    return crc; 
} 

Il problema è che dovrei ottenere due byte esadecimali come Numeri CRC mentre questa funzione restituisce un valore intero. Ad esempio, per "01" (1 byte), dovevo ottenere un "7E80" mentre ottengo "21695", e non riesco a fare una sorta di conversione da questo a quei dati esadecimali.

La mia domanda, quindi, è: come passare dal risultato intero al risultato di doppio esadecimale necessario? Ho provato un paio di opzioni, senza successo.

Sono contento per qualsiasi aiuto,

Momergil.

Nota: sto usando Qt, quindi se si potesse trovare una soluzione attuazione QByteArray o un altro codice Qt amichevole, sarò felice. In entrambi i casi, una soluzione che non utilizza Qt, C o C++ è inutile: P

+0

Formattazione, la stai stampando come * decimale * anziché * esadecimale *. Prova ad es. 'std :: cout << std :: hex << valore << '\ n';'. Sebbene il valore decimale '21695' non sia lo stesso esadecimale' 0x7e80'. –

+0

La domanda è simile a ** [questa] (http://stackoverflow.com/questions/19358246/crc-ccitt-to-crc16-modbus-implementation/19382244#19382244) **, ma per un linguaggio di programmazione diverso. Tuttavia, potrebbe comunque essere interessante se si desidera calcolare il CRC senza utilizzare un tavolo. – avra

risposta

3

Secondo MODBUS over serial line specification and implementation guide V1.02, il CRC viene inviato little-endian (prima il byte basso).

ho idea, però, come si avvicinò con l'uso di alcun byte esadecimali per la CRC. MODBUS RTU è un protocollo binario e il CRC viene inviato come due byte, non come quattro cifre esadecimali!

Ecco come procedere, utilizzando la funzione CRC16 fornita.

QByteArray makeRTUFrame(int slave, int function, const QByteArray & data) { 
    Q_ASSERT(data.size() <= 252); 
    QByteArray frame; 
    QDataStream ds(&frame, QIODevice::WriteOnly); 
    ds.setByteOrder(QDataStream::LittleEndian); 
    ds << quint8(slave) << quint8(function); 
    ds.writeRawData(data.constData(), data.size()); 
    int const crc = CRC16((BYTE*)frame.constData(), frame.size()); 
    ds << quint16(crc); 
    return frame; 
} 
+0

Grazie per la rapida risposta. Bene, non dimenticando la necessità di un (BYTE *) prima di data.constData() nella funzione CRC16, sei sicuro che questa funzione funzioni? : P Ho provato a testarlo su un foglio Excel da SimplyModbus.ca e non sono riuscito a far corrispondere i valori CRC (nota: selezionare il QByteArray risultante e mostrare in qdebug() come Hex()). Mi fa piacere se lo provi con youserlf :) – Momergil

+0

@Momergil: Il CRC doveva essere calcolato sull'intero frame. Ho modificato il codice per risolverlo. Questo dimostra che stai cercando di usare un codice che non comprendi nelle impostazioni di automazione industriale. Spero che nessuno venga ucciso. –

+0

Haha, nessuna preoccupazione :) Mi stavo chiedendo: nel protocolo Modbus è necessario montare il pacchetto con: indirizzo salve (ok), comando/funzione (ok) e poi il numero di registro e poi i dati - mentre la funzione ha solo i dati. In questo caso, come dovrei inserirlo nella tua funzione? Solo aggiungendo un << quint8 (registro)? Nota: non sono stato in grado di verificare se il CRC è stato eseguito correttamente anche da esso. Per sicurezza, hai controllato se il tuo calcolo CRC funziona utilizzando, ad esempio, Semplicemente il calcolatore CRC di Modbus? xD (Spero non ti sia arrabbiato per me chiedendo che ^^) – Momergil

6
unsigned int CRC16_2(unsigned char *buf, int len) 
{ 
    unsigned int crc = 0xFFFF; 
    for (int pos = 0; pos < len; pos++) 
    { 
    crc ^= (unsigned int)buf[pos]; // XOR byte into least sig. byte of crc 

    for (int i = 8; i != 0; i--) { // Loop over each bit 
    if ((crc & 0x0001) != 0) {  // If the LSB is set 
     crc >>= 1;     // Shift right and XOR 0xA001 
     crc ^= 0xA001; 
    } 
    else       // Else LSB is not set 
     crc >>= 1;     // Just shift right 
    } 
    } 

    return crc; 
} 

im genere di un noob me, butttt-

ho usato il codice u forniti e verificati io stesso, e come u detto non ha funzionato bene, ma poi ho capito che passava caratteri esadecimali, quindi ho appena cambiato uint in char e si verifica almeno per me.

ho anche calcolato un campione a mano per ricontrollare.

+0

@Roney Thansk Roney per la risposta, anche se finirò usando la versione di Kuba da quando l'ho già convalidato =] – Momergil

+1

@Momergil: è la risposta di Adam. Roney lo stava modificando leggermente. – lpapp

+0

@Momergil: Yeap. Crediti ad Adam Lam. –

2

Ho provato a utilizzare il primo esempio di codice che hai postato qui (quello che utilizza la tabella) e ho scoperto che c'è un errore nell'uso dell'indice. Per fare in modo che il codice funzioni correttamente, è necessario accedere alla tabella nell'area limitata dalla sua dimensione.

wCRCWord ^= wCRCTable[(nTemp & 0xFF)]; 

Quindi l'intero codice, che restituisce il valore corretto di CRC16 per MODBUS è elencato di seguito. Il numero restituito ha già scambiato il byte Lo e Hi.

WORD CRC16 (const BYTE *nData, WORD wLength) 
{ 
    static const WORD wCRCTable[] = { 
    0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 
    0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, 
    0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 
    0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, 
    0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, 
    0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, 
    0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, 
    0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, 
    0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, 
    0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, 
    0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 
    0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, 
    0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, 
    0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, 
    0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, 
    0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, 
    0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, 
    0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, 
    0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, 
    0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, 
    0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, 
    0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 
    0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, 
    0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, 
    0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, 
    0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, 
    0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, 
    0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, 
    0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, 
    0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, 
    0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, 
    0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040 }; 

    BYTE nTemp; 
    WORD wCRCWord = 0xFFFF; 

    while (wLength--) 
    { 
     nTemp = *nData++^wCRCWord; 
     wCRCWord >>= 8; 
     wCRCWord ^= wCRCTable[(nTemp & 0xFF)]; 
    } 
    return wCRCWord; 
} // End: CRC16 
+0

Hai detto che * Il numero restituito ha già scambiato Lo e Ciao byte *. Ne sei sicuro per il codice presentato? Penso che questo codice restituisca una normale parola CRC, e un utente deve scambiare byte prima di concatenare una cornice di messaggi, come segue: 'wCRCWord = (wCRCWord << 8) | (WCRCWord >> 8) '. Ad esempio, considera un semplice messaggio in esadecimale: "11 03 00 6B 00 03'. Il valore CRC calcolato da questo codice per il frame considerato è '87 76', e il messaggio completo è' 11 03 00 6B 00 03 76 87' (esempio da qui: http://www.simplymodbus.ca/ASCII.htm) –

0

Ecco i miei due centesimi. La prima cosa che non puoi restituire due valori a una funzione in modo

  1. aggiungi due valori alla matrice (ricordate di rimuovere const)
  2. ritorno una struttura che contiene questi due valori.
  3. Proccess valore di ritorno come segue

    WORD n = CRC16(nData,wLength); 
    WORD x = (0xFF00&n)>>8,y=0x00FF&n; 
    printf("0x%04x\n", n); // to check original value 
    printf("0x%02x\t0x%02x\n",x,y); // to check separated values 
    

Prova questa e fammi sapere.