peteryi
级别: 正式会员
精华主题: 0
发帖数量: 7 个
工控威望: 97 点
下载积分: 583 分
在线时间: 2(小时)
注册时间: 2014-10-28
最后登录: 2019-07-31
查看peteryi的 主题 / 回贴
楼主  发表于: 2014-10-28 19:56
最近手上有个威纶通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重新编辑 ]