最近手上有个威纶通MT6056I的HMI,需要与公司的一款板卡通讯,板卡遵循的是自由协议,但是采用的校验方式CRC-16/XMODEM.
这种校验方式是CRC16校验方式的一种,但是与MODBUS协议的CRC16的生成方式不同。威纶通的脚本语言库中有CRC校验函数,但是这个校验
函数是CRC-16/MODBUS版本的,不能为我所用,所以我计划自己设计一个CRC16/XMODEM的函数,然后将这个函数保存到函数库里,以备他用
。由于第一次使用这个屏幕,经验不做,遇到一些困难,但也努力解决了。
首先不得不吐槽下,威纶通的宏指令说明书写的太简单了,在遇到问题的时候,可能无法从指令的说明手册上找到答案,更多的是自己摸索。比如
自己写子函数时就遇到狗血的问题
1:数组不能作为函数参数
比如
sub short function(char dat[],char len)
其中 char dat[] 参数将出错。
解决方法,后面有表述
2: 在调用子函数的时候,在函数的参数中不能出现常量,只能是变量的方式
例如我定义了子函数 function( short a,short b)
调用方式 function( 1000,1000) 编译器将会告诉你参数类型错误
当我改成如下掉用方式就可以了
short i=1000
short j=1000
function(i,j)
好进入正题吧,如何写个CRC校验函数,其实根本问题,是如何将一串数据传给子函数,子函数将传过来的数据根据特定算法,运算出计算结果,关于CRC算法,本文不做论述,只提供代码。
我首先想到的是这样的思路:定义 这样一个函数 short CRC16(short dat[],short len),用来计算CRC。
但是却遇到了上面1中的问题,数组参数无法作为函数的形参,官方也找不到解决方法。
最后想到的解决方法是在LW存储区开辟一块暂存区域,将要进行CRC计算的数据搬运到这块暂存区域上。再CRC校验函数中根据LW的地址将数据取出,进行CRC计算。
如此可解决无法传递数组参数的问题。操作如下。
定义 sub short CRC_16(short dataddr, short len)
参数说明 short dataddr,dataddr是位于LW暂存区的起始地址,short len len 数据长度。
下面红色区域为重点区域,注意理解。
sub short CRC_16(short dataddr, short len)
short i, j
unsigned short crc_reg = 0x0000
unsigned short current
unsigned char dattmp[128] //申请一定长度的数组来保存要进行校验的数据。
GetData(dattmp[0], "Local HMI", LW, dataddr, len)//将暂存区的数据复制到dattmp中 len=len-1
for i = 0 to len
current = dattmp
current=current<<8
crc_reg=crc_reg^current
for j = 1 to 8
if (crc_reg & 0x8000) <> 0 then
crc_reg = (crc_reg << 1) ^ 0x1021
else
crc_reg=crc_reg << 1
end if
next
next
return crc_reg;
end sub
上面绿色部分是进行CRC计算的,这里不做研究。下面来讲讲红色部分。红色部分就是申请数组空间,然后,将LW,暂存空间的数据,转移到所申请的数组中,交给下面计算。
这里的疑惑是为何要申请数组,然后在拷贝数据,这么麻烦,而不是用下面的方式进行,下面的算法是每次循环开始先读取暂存空间数据,先不说牺牲时间什么的,最起码这
中不用申请上面那128大的数组。理论可行,但是实际上确实错误的。其主要GetData和SetData 函数实现原理,以及数据在LW中存储方式不清楚造成的。
sub short CRC_16_2(short dataddr, short len)
short i, j
unsigned short crc_reg = 0x0000
unsigned short current
unsigned char dattmp
short addr
addr=dataddr
len=len-1
for i = 0 to len
GetData(dattmp, "Local HMI", LW, addr, 1) addr=addr+1
current = dattmp
current=current<<8
crc_reg=crc_reg^current
for j = 1 to 8
if (crc_reg & 0x8000) <> 0 then
crc_reg = (crc_reg << 1) ^ 0x1021
else
crc_reg=crc_reg << 1
end if
next
next
return crc_reg;
end sub
GetData和SetData 函数实现原理,以及数据在LW中存储方式
先来说说LW中的数据存储 (吐槽:为何网上关于这方面的资料很少,几乎没有LW存储空间的详细说明)
目前 笔者使用 软件 EB8000 V4.65
LW 可理解为计算机的RAM ,掉电数据不保存,但是存取速度快。每个存储单元是16bit。分为高字节 (bit15-bit8)和低字节(bit7 -bit 0)
bit15 bit0
例如 0x1234 存储方式为高字节 0x12 ,低字节0x34.
SetData 当用SetData 来写LW中的数据的时候,会根据第一个参数的类型来指导操作
如下程序,这是正常的操作程序。a的类型是short
unsigned short a=0x1234
unsigned char b[2]
unsigned char c
SetData(a, "Local HMI", LW, 0, 1)
GetData(c, "Local HMI", LW, 0, 1)
GetData(b[0], "Local HMI", LW, 0, 2)
TRACE("C = %d", c) //c=0x34
TRACE("b[0] = %d", b[0]) //b[0]=0x34
TRACE("b[1] = %d", b[1]) //b[1]=0x12
但是SetData的第一个参数是第一个char类型的数组如下
unsigned short a
unsigned char b[2]
b[1]=0x12
b[0]=0x34
SetData(b[0], "Local HMI", LW, 0, 2)
GetData(a, "Local HMI", LW, 0, 1)
TRACE("a = %d", a) //a=0x1234
可以看出,在保存char 型数据的时候,为了节省空间进行了特别处理
理论上b[0]应该保存在LW0000,b[1]保存在LW0001.但是实际上确实b[0]保存在 LW0000的低字节,b[1]保存在LW0000的高字节。
同样的道理GetData 也遵循相同的操作。
现在能回答为何不能采用第二种子函数写法.主要原因是 每次循环都是读取一个字的底字节。高字节数据被丢弃了。
假如写入内存的是b[0]到b[10],则通过下面循环读取的是b[0],b[2],b[4].....,请好好体会。
for i = 0 to len
GetData(dattmp, "Local HMI", LW, addr, 1)
addr=addr+1
current = dattmp
next
再需要调用CRC函数的时候,可用如下的方法操作
char sendbus[26]
short lwadd=7000 //lw 暂存区开始地址
short len=26
SetData(sendbuf[1], "Local HMI", LW, lwadd, len) //将得计算的数阻搬到LW7000开始的空间,具体在什么地址,可自己安排,只要注意LW 范 //0-9000
tmp = CRC_16(lwadd, len) //CRC计算
版权申明:本文章由逸创论坛(
www.yeecon.com)原创,转载请标明出处:
http://www.yeecon.com/forum.php?mod=viewthread&tid=74&fromuid=1(出处: 逸控BBS)
由于本人知识有限,文中如有错误,请告知。
作者:semonpic
E-Mail:
semonping@163.com企鹅号 442999791
[ 此帖被peteryi在2014-10-28 20:45重新编辑 ]