I have a terminal that communicates through RS232 COM with the computer. The protocol that I was given says that I have to send a certain combination of bytes and the CRC 16 IBM calculation of the data sent at the end
I was also given a C written application that I can test with, that application writes a log with send data and received data. In that log I see if I send the terminal this string hexString = "02 00 04 a0 00 01 01 03"
. I must also send this CRC16 IBM result of data 06 35.
I have managed to somehow translate the C method that was given as an example, into C#. But my result is far away from what I know I must receive.
I have tested sending the data from the log and everything is fine. I must have my calculation done wrong. Am I doing anything wrong here?
Here is my code:
CRC class:
public enum Crc16Mode : ushort
{
ARINC_NORMAL = 0XA02B, ARINC_REVERSED = 0xD405, ARINC_REVERSED_RECIPROCAL = 0XD015,
CCITT_NORMAL = 0X1021, CCITT_REVERSED = 0X8408, CCITT_REVERSED_RECIPROCAL = 0X8810,
CDMA2000_NORMAL = 0XC867, CDMA2000_REVERSED = 0XE613, CDMA2000_REVERSED_RECIPROCAL = 0XE433,
DECT_NORMAL = 0X0589, DECT_REVERSED = 0X91A0, DECT_REVERSED_RECIPROCAL = 0X82C4,
T10_DIF_NORMAL = 0X8BB7, T10_DIF_REVERSED = 0XEDD1, T10_DIF_REVERSED_RECIPROCAL = 0XC5DB,
DNP_NORMAL = 0X3D65, DNP_REVERSED = 0XA6BC, DNP_REVERSED_RECIPROCAL = 0X9EB2,
IBM_NORMAL = 0X8005, IBM_REVERSED = 0XA001, IBM_REVERSED_RECIPROCAL = 0XC002,
OPENSAFETY_A_NORMAL = 0X5935, OPENSAFETY_A_REVERSED = 0XAC9A, OPENSAFETY_A_REVERSED_RECIPROCAL = 0XAC9A,
OPENSAFETY_B_NORMAL = 0X755B, OPENSAFETY_B_REVERSED = 0XDDAE, OPENSAFETY_B_REVERSED_RECIPROCAL = 0XBAAD,
PROFIBUS_NORMAL = 0X1DCF, PROFIBUS_REVERSED = 0XF3B8, PROFIBUS_REVERSED_RECIPROCAL = 0X8EE7
}
public class Crc16
{
readonly ushort[] table = new ushort[256];
public ushort ComputeChecksum(params byte[] bytes)
{
ushort crc = 0;
for (int i = 0; i < bytes.Length; ++i)
{
byte index = (byte)(crc ^ bytes[i]);
crc = (ushort)((crc >> 8) ^ table[index]);
}
return crc;
}
public byte[] ComputeChecksumBytes(params byte[] bytes)
{
ushort crc = ComputeChecksum(bytes);
return BitConverter.GetBytes(crc);
}
public Crc16(Crc16Mode mode)
{
ushort polynomial = (ushort)mode;
ushort value;
ushort temp;
for (ushort i = 0; i < table.Length; ++i)
{
value = 0;
temp = i;
for (byte j = 0; j < 8; ++j)
{
if (((value ^ temp) & 0x0001) != 0)
{
value = (ushort)((value >> 1) ^ polynomial);
}
else
{
value >>= 1;
}
temp >>= 1;
}
table[i] = value;
}
}
}
Method to process the bytes received:
public ushort CalculateCRC(byte[] data)
{
Crc16 crcCalc = new Crc16(Crc16Mode.IBM_NORMAL);
ushort crc = crcCalc.ComputeChecksum(data);
return crc;
}
In this method you can select polynomial from the Enum.
Main in Program Class:
static void Main(string[] args) { try { Metode m = new Metode();
string hexString = "02 00 04 a0 00 01 01 03";
byte[] bytes = m.HexStringToByteArray(hexString);
ushort crc = m.CalculateCRC(bytes);
string hexResult;
int myInt = crc;
hexResult = myInt.ToString("X");
//Console.WriteLine(crc.ToString());
Console.WriteLine(hexResult);
Console.ReadLine();
}
catch (Exception ex)
{
Metode m = new Metode();
m.writeError(ex.Message);
}
}
Convert from hexstring to byte array:
public byte[] HexStringToByteArray(string hexString)
{
hexString = hexString.Replace(" ", "");
return Enumerable.Range(0, hexString.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hexString.Substring(x, 2), 16))
.ToArray();
}
Convert from byte array to hex string:
public string ByteArrayToHexString(byte[] byteArray)
{
return BitConverter.ToString(byteArray);
}
What am I doing wrong here?
UPDATE:
Thanks to @MarkAdler I have managed to translate the calculation. What I didn't notice until late was the fact that the CRC calculation should have been for online the DATA sent to the terminal, NOT the entire message!
So the hexString should have been in fact "a0 00 01 01", the data without the STX/length/ETX.
Here is the code for this particular CRC16 Calculus in C#:
public ushort CalculateCRC(byte[] data, int len)
{
int crc = 0, i = 0;
while (len-- != 0)
{
crc ^= data[i++] << 8;
for (int k = 0; k < 8; k++)
crc = ((crc & 0x8000) != 0) ? (crc << 1) ^ 0x8005 : (crc << 1);
}
return (ushort)(crc & 0xffff);
}
You'd need to provide more information on the specification you are trying to implement. However I can tell right away that you are using the wrong polynomial. The CRC routines are shifting right, which means that the polynomial should be bit-reversed. As a result, IBM_NORMAL
cannot be correct.
While IBM_REVERSED
would be an appropriate polynomial for shifting right, that may or may not be the polynomial you need to meet your specification. Also there could be exclusive-or's coming into or leaving the CRC routine that are needed.
Update:
The linked documentation provides actual code to compute the CRC. Why aren't you looking at that? Finding random code on the interwebs to compute a CRC without looking at what's in the documentation is not likely to get you far. And it didn't.
The documented code shifts the CRC left, opposite of the code that you posted in the question. You need to shift left. The polynomial is 0x8005
. There is no final exclusive-or, and the initial CRC value is zero.
Here is a simplified version of the code in the document, written in C (this code avoids the little-endian assumption that is built into the code in the document):
#include <stddef.h>
typedef unsigned char byte;
typedef unsigned short ushort;
ushort crc16ecr(byte data[], int len) {
ushort crc = 0;
for (int i = 0; i < len; i++) {
crc ^= (ushort)(data[i]) << 8;
for (int k = 0; k < 8; k++)
crc = crc & 0x8000 ? (crc << 1) ^ 0x8005 : crc << 1;
}
return crc;
}
Per the document, the CRC is computed on the tag, len, and data, which for your message is a0 00 01 01
. Not the whole thing. (Reading the documentation thoroughly is always an excellent first step.) Running that through the CRC code in the document, you get 0x0635
. The document says that that is transmitted most significant byte first, so 0x06 0x35
.