原作者姓名 戚高
介绍
这几天一个朋友在一个现场调试一个工业控制项目,由于临时更换设备使得原来已经编写好的底层通讯程序不得不重新编写,通讯协议也进行了很大的更改。新设备采用标准的Modubs-RTU协议进行数据的通讯。Modubs协议是一种广泛使用的协议,采用16位CRC校验。朋友在校验这里出了问题,由于现场查找资料等信息不方便委托我将一系列的字节数据(发送命令报文)进行校验然后将校验结果回复他。为了方便起见,我写了一个专门用于Modubs协议通讯校验的16位CRC校验程序给他。程序虽然比较简单,但是中间的数据类型转换,进制转换以及计算校验等觉得可能对正在寻求这个方面信息的人有帮助,所以放上来供大家共同研究一下!^_^
正文
CRC原理以及实现:
随着计算机技术的不断发展,在现代工业中,利用微机进行数据通讯的工业控制应用得也越来越广泛。由于传输距离、现场状况等诸多可能出现的因素影响,计算机与受控设备之间的通讯数据常会发生无法预测的错误。为了防止错误所带来的影响,一般在通讯时采取数据校验的办法,而循环冗余码校验是最常用的校验方法之一。循环冗余码校验原理循环冗余码校验英文名称为Cyclical Redundancy Check,简称CRC。它是利用除法及余数的原理来作错误侦测(Error Detecting)的。实际应用时,发送装置计算出CRC值并随数据一同发送给接收装置,接收装置对收到的数据重新计算CRC并与收到的CRC相比较,若两个CRC值不同,则说明数据通讯出现错误。根据应用环境与习惯的不同,CRC又可分为以下几种标准:①CRC-12码;②CRC-16码;③CRC-CCITT码;④CRC-32码。CRC-12码通常用来传送6-bit字符串。CRC-16及CRC-CCITT码则用是来传送8-bit字符,其中CRC-16为美国采用,而CRC-CCITT为欧洲国家所采用。CRC-32码大都被采用在一种称为Point-to-Point的同步传输中。下面以最常用的CRC-16为例来说明其生成过程。CRC-16码由两个字节构成,在开始时CRC寄存器的每一位都预置为1,然后把CRC寄存器与8-bit的数据进行异或,之后对CRC寄存器从高到低进行移位,在最高位(MSB)的位置补零,而最低位(LSB,移位后已经被移出CRC寄存器)如果为1,则把寄存器与预定义的多项式码进行异或,否则如果LSB为零,则无需进行异或。重复上述的由高至低的移位8次,第一个8-bit数据处理完毕,用此时CRC寄存器的值与下一个8-bit数据异或并进行如前一个数据似的8次移位。所有的字符处理完成后CRC寄存器内的值即为最终的CRC值。下面为CRC的计算过程:1.设置CRC寄存器,并给其赋值FFFF(hex)。2.将数据的第一个8-bit字符与16位CRC寄存器的低8位进行异或,并把结果存入CRC寄存器。3.CRC寄存器向右移一位,MSB补零,移出并检查LSB。4.如果LSB为0,重复第三步;若LSB为1,CRC寄存器与多项式码相异或。5.重复第3与第4步直到8次移位全部完成。此时一个8-bit数据处理完毕。6.重复第2至第5步直到所有数据全部处理完成。7.最终CRC寄存器的内容即为CRC值。这样我们就可以得出计算校验的函数封装了。
WORD CCalCrcDlg::Caluation_CRC16(BYTE Buff[], int nSize)
{
WORD m_Crc;
WORD m_InitCrc = 0xffff;
unsigned short i,j;
for(i=0; i<nSize; i++)
{
m_InitCrc ^= Buff[i];
for(j=0; j<8; j++)
{
m_Crc = m_InitCrc;
m_InitCrc >>= 1;
if(m_Crc&0x0001)
m_InitCrc ^= 0xa001;
}
}
return m_InitCrc;
}
这样我们可以定制一个计算CRC校验的界面了。由于针对Modubs的一个Demo,Modubs发送命令格式为:地址,功能码,读寄存器高地址,读寄存器低地址,读取字节个数高地址,读取字节个数低地址,校验低,校验高。所以我们定制的界面运行如下:
我们来编写一个从界面获得CString类型十六进制数据转换成为int类型的函数封装以及方向函数封装。
DWORD CCalCrcDlg::H2D(CString B)
{
DWORD D;
DWORD tmpD;
B.Remove (' ');
CString tmpBit;
int BitLen = B.GetLength();
for (int i=0; i<BitLen; i++)
{
tmpBit = B.Mid(BitLen-i-1,1);
if (tmpBit == "A" || tmpBit == "a") tmpD = 10;
else if (tmpBit =="B" || tmpBit == "b") tmpD =11;
else if (tmpBit =="C" || tmpBit == "c") tmpD =12;
else if (tmpBit =="D" || tmpBit == "d") tmpD =13;
else if (tmpBit =="E" || tmpBit == "e") tmpD =14;
else if (tmpBit =="F" || tmpBit == "f") tmpD =15;
else sscanf( tmpBit, "%d", &tmpD );
if (i==0)
D = tmpD;
else
{
tmpD = tmpD * (2<<(i*4-1));
D = D + tmpD;
}
}
return D;
}
CString CCalCrcDlg::D2H(DWORD D)
{
CString H , tmpH;
bool tmpflag = false;
DWORD tmpD;
tmpD = D;
while((D = D / 16) !=0)
{
tmpH.Format("%d", tmpD % 16);
if (tmpH == "10") tmpH = "A";
if (tmpH == "11") tmpH = "B";
if (tmpH == "12") tmpH = "C";
if (tmpH == "13") tmpH = "D";
if (tmpH == "14") tmpH = "E";
if (tmpH == "15") tmpH = "F";
tmpD = D;
if (!tmpflag)
{
H = tmpH;
tmpflag = true;
}
else
H = tmpH+H;
}
tmpH.Format("%d",tmpD);
if (tmpH == "10") tmpH = "A";
if (tmpH == "11") tmpH = "B";
if (tmpH == "12") tmpH = "C";
if (tmpH == "13") tmpH = "D";
if (tmpH == "14") tmpH = "E";
if (tmpH == "15") tmpH = "F";
H = tmpH+H;
int strLen = H.GetLength();
for (int i=1; i<strLen; i++)
{
{
H.Insert(strLen-i,"");
}
}
H = "0x" + H;
return H;
}
计算按钮触发计算处理可以为:
void CCalCrcDlg::OnCaluation()
{
// TODO: Add your control notification handler code here
CString szVal1,szVal2,szVal3,szVal4,szVal5,szVal6,szVal7,szVal8;
m_1.GetWindowText(szVal1);
m_2.GetWindowText(szVal2);
m_3.GetWindowText(szVal3);
m_4.GetWindowText(szVal4);
m_5.GetWindowText(szVal5);
m_6.GetWindowText(szVal6);
BYTE bCalBuff[6] = {H2D(szVal1)&0x0ff,H2D(szVal2)&0x0ff,H2D(szVal3)&0x0ff,/
H2D(szVal4)&0x0ff,H2D(szVal5)&0x0ff,H2D(szVal6)&0x0ff};
WORD bCrc = Caluation_CRC16(bCalBuff,6);
BYTE lCrcBuff,hCrcBuff;
lCrcBuff = bCrc&0x0ff;
hCrcBuff = (bCrc>>8)&0x0ff;
szVal7 = D2H(lCrcBuff);
szVal8 = D2H(hCrcBuff);
m_7.SetWindowText(szVal7);
m_8.SetWindowText(szVal8);
}
CRC16位校验应用在通讯的各个方面,这个Demo中距离了CRC校验的实现和调用,这里调用实现了长度为6的校验结果计算,同时我们可以使用这个计算校验函数完成更多长度的CRC校验。由于时间比较仓促,中间有很多的小bug或者不足,希望有问题和我探讨。我的联系方法是:13975102873@hnmcc.com
更多技术文章请参看施昌权的个人网站: http://www.joyvc.cn