oldbeginner 发表于 2013-11-8 15:42:57

开源PLC学习笔记03(再从51开始 通讯)——2013_11_8

本帖最后由 oldbeginner 于 2013-11-8 15:45 编辑

感觉通讯这块真得很难,首先STM32等通讯代码不提,只看简易PLC的通讯代码就很复杂,其中还夹杂着IAP,我IAP还没有学习,不过边研究边学吧。
*************************************************
找到51实现通讯的代码有:
1、简易PLC v1.1.5版 (成型2009年10月)
2、简易PLC v1.0.1版http://www.amobbs.com/thread-3579264-1-4.html (成型2009年9月)
3、znl3512的AT89S51单片机仿三菱FX1N PLC 下载程序 (成型2009年6月)http://www.amobbs.com/thread-3608835-1-2.html
4、biscuit2的三菱FX1N下载协议的51代码例子 http://www.ourplc.com/2011-11/25/1846281673.html (成型2011年11月)

**************************************************
简单看了一下,没有一个能理清思路的。就简不就繁,选择简易PLC v1.0.1来分析,感觉v1.0.1的注释比v1.1.5要详细,而且也能通过通讯测试。biscuit2的代码最新,但是太长而且没有注释,不是初学者的菜。

和刚开始看PLC代码教训一样,即使通讯只是一部分,也超过我目前的理解力。直接看成型的行不通,于是按图索骥又找到了几个帖子:

1、三菱FX系列PLC下载通信协议说明-----开源 PLC 编程重点参考资料

2、三菱FX-PLC 的通讯协议参考

**************************************************
学习思路如下:只实现FX1N的通讯,因为资料比较齐全;只关注要用到的通讯指令,并利用串口通讯程序模拟一下,只确认报文输入是否和资料一致,反正我也不会串口监控;把整个通讯程序流程和实现理解清楚;移植通讯程序到某个能验证的MCU上。

oldbeginner 发表于 2013-11-8 16:01:28



在图片所显示的帖子中,PC和PLC通讯的过程被展示出来。如果不是代码太长,而且过程复杂,就是一个普通的报文程序,所以看看能否让代码更简洁些,这样更容易理解。





以上就是已知的格式,看是否能简化。

oldbeginner 发表于 2013-11-8 20:02:49

本帖最后由 oldbeginner 于 2013-11-8 22:00 编辑

oldbeginner 发表于 2013-11-8 16:01 static/image/common/back.gif
在图片所显示的帖子中,PC和PLC通讯的过程被展示出来。如果不是代码太长,而且过程复杂,就是一个普通的 ...

选择简易PLC v1.0.1看来不是明智的,通讯就是一个函数FX1NProcessing。但是在V1.0.1版中,这个函数对我来说是一个巨无霸,复制到word当中有9页,字符数是9000多。在看这个函数过程中,怀疑,急躁,沮丧各种非正能量接踵而至,根据经验,该从其中撤了。

****************
还是转到v1.1.15,这次好多了,函数FX1NProcessing复制到word当中只有5页,字符数量也少一半。当还是太大,为了分解这个函数,理了一下结构。


有了这张图就可以重点突破了,选择read code作为突破口。read code只有1页,字符数量也是有955个,相比较刚开始看的一下少了90%的内容,难度也自然会下降一个数量级。
read code也可以分成三个部分:
1、计算得到读取地址;
2、计算得到读取字节数;
3、计算 sumdata 并发送读取字节。

感觉可以入手具体分析了。

补充:附上通讯函数。

oldbeginner 发表于 2013-11-8 20:58:46

本帖最后由 oldbeginner 于 2013-11-8 21:05 编辑


//      Read code 功能第一部分,计算得到读取地址:

for(i=4;i<8;i++)UartReceiveBuffer=ascto0F(UartReceiveBuffer);
ReadAddr =UartReceiveBuffer<<12;
ReadAddr+=UartReceiveBuffer<<8;
ReadAddr+=UartReceiveBuffer<<4;
ReadAddr+=UartReceiveBuffer;

这里为了代码简洁,把UartReceiveBuffer写为Buffer,串口接收缓冲直接写成缓冲

for(i=4;i<8;i++)
   Buffer=ascto0F(Buffer);

ReadAddr =Buffer<<12;
ReadAddr+=Buffer<<8;
ReadAddr+=Buffer<<4;
ReadAddr+=Buffer;

**************************

现在就可以了解其中的定义了,

//接收数据缓冲(定义在uart.c中),全局变量
volatile unsigned char UartReceiveBuffer;      

又有一个关键字要学了,至少入门资料没用到过,volatilevolatile的作用: 作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。http://baike.baidu.com/link?url= ... qe8-xRWpsl3nn_Xq3qq,看这个链接还是挺复杂的,不过暂时了解一下就可以了,目前重点不在这里。

有一个InLEN需要找到定义(在uart.h中)
//      超时收 接收缓冲区长度      
#define InLEN                        142               

转化为自己的理解,Buffer就是一个无符号字符数组,为什么142?等到后面看会不会自动出现解释。
unsigned char Buffer;

ReadAddr是在通讯函数中定义的一个局部变量,根据表面字意,就是要读的地址
unsigned intReadAddr=0;

unsigned int和unsigned char同时出现,uchar是一个8位无符号数,表示范围0到255,而uint是十六位无符号数,表示范围0到65535。http://zhidao.baidu.com/link?url ... CMvq_8B8KAjkTZ1c83K

ascto0F是在同一个文件中定义的函数,功能描述:将ASCII 转换成0~F。ascto0F是一个短小的函数,而且功能描述清晰,直接拿来使用即可,这样可以把精力都集中在要理解的功能上。

ReadAddr =Buffer<<12;
在理解这个赋值上有些疑惑,找了一个资料http://bbs.21ic.com/icview-178995-1-1.html。
感觉要实现的功能是把Buffer的低4位移到ReadAddr的高4位;

然后
ReadAddr=ReadAddr+Buffer<<8;
然后再让ReadAddr的高8位和Buffer相加作为新的高8位;

然后
ReadAddr=ReadAddr+Buffer<<4;
然后再让ReadAddr的中间8位和Buffer相加作为新的ReadAddr;

最后,
ReadAddr=ReadAddr+Buffer;
最后让ReadAddr的低8位和Buffer相加作为新的ReadAddr。

如果没猜错,Buffer到Buffer就是接收报文对应的地址位置,而且地址就像上面的计数一样。暂时不继续向下了解Read code功能,下一步要查看通讯协议确认一下。

lg22331 发表于 2013-11-8 21:21:01

太强了,支持触摸屏不?

oldbeginner 发表于 2013-11-8 21:32:06

本帖最后由 oldbeginner 于 2013-11-8 21:36 编辑

oldbeginner 发表于 2013-11-8 20:58 static/image/common/back.gif
//      Read code 功能第一部分,计算得到读取地址:

for(i=4;i

看了一下http://www.amobbs.com/thread-3394474-1-1.html里的通讯协议,估计是正确的,另外也可以理解为什么Buffer[]要4位4位左移。


因为Buffer=ascto0F(Buffer);
Buffer的值是从0到F,所以高4位都是0。

ReadAddr =Buffer<<12;
ReadAddr+=Buffer<<8;
ReadAddr+=Buffer<<4;
ReadAddr+=Buffer;

例如
Buffer=8
Buffer=0
Buffer=2
Buffer=E

则ReadAddr=802E。
*************************
至此,Read code功能的第一部分了解,可以开始了解第二部分了。

oldbeginner 发表于 2013-11-8 22:10:51

本帖最后由 oldbeginner 于 2013-11-9 16:32 编辑

小结一下报文再继续
例子
STX ,   CMD ,    ADDRESS,               BYTES,ETX,SUM
02H,    30H,   31H,30H,46H,36H,    30H,34H, 03H, 37H,34H

指令解释如下:
STX,         "E01",         "10F6",         "04",                ETX,         "74"
报文开始,    读命令,   地址10F6H处,   04H字节数据,   报文结束,   累加方式和校验
BufferBuffer   Buffer      Buffer      Buffer      Buffer

其中 SUM=CMD+......+ETX=30h+31h+30h+46h+36h+30h+34h+03h=74h;
累加和超过两位取低两位,转换成ascii码,分 SUM(upper),SUM(lower)二次传送。
                                                            Buffer      Buffer


边研究代码边学习协议看起来可行。

进入Read code的第二部分

//      计算得到读取字节数:
for(i=8;i<10;i++)Buffer=ascto0F(Buffer);
ReadLen = Buffer*16+Buffer;
UartSendchar(STX)

首先
for(i=8;i<10;i++)
       Buffer=ascto0F(Buffer);
得到Buffer和Buffer转换值,在上面的报文协议中可知,Buffer和Buffer表示要读出的字节数,

剩余代码,
ReadLen = Buffer*16+Buffer;
UartSendchar(STX);

其中,
ReadLen是定义在这个通讯函数内部的局部变量,      unsigned char ReadLen=0;
因为Buffer和Buffer组成一个16位的数,保存要读取的字节数,高位是Buffer,所以Buffer*16就是高位的值,再加上Buffer就完整了。
然后
UartSendchar(STX);
根据字面理解,串口发出STX指令,需要确认一下协议和该函数的定义。

// 函数名称: UartSendchar
//uart.c中定义
// 功能描述:放入一个字节到发送缓冲区

UartSendchar这个函数代码有点多,暂时了解功能即可,继续研究协议,为什么要发送STX。

找到定义(FX1N.h)
#define ENQ         0x05                        //请求
#define ACK         0x06                         //PLC 接收正确响应
#define NACK         0x15                         //PLC 接收错误响应
#define STX         0x02                         //报文开始
#define ETX         0x03                         //报文结束


PLC回答是以STX开头的,这也可以理解了。

可以进入Read code第三部分了。

oldbeginner 发表于 2013-11-9 13:08:33

oldbeginner 发表于 2013-11-8 22:10 static/image/common/back.gif
小结一下报文再继续
例子
STX ,   CMD ,    ADDRESS,               BYTES,ETX,SUM


//Read code第三部分,计算 sumdata 并发送读取字节
                  sumdata=0;
                  for(i=1;i<=ReadLen;i++)
                  {
                        if(ReadAddr==0x01c0)
                              Readdat=0x0a;            //    此地址,目前只见过读,没有见过写.
                        else
                        {
                           //    读FlashData
                            Readdat=IAPFlashReadMode(ReadAddr+i-1);   
                        }

                        tempascdata=hextoasc(Readdat);
                        ucdata=tempascdata>>8;
                        sumdata = sumdata + ucdata;
                        UartSendchar(ucdata);
                        ucdata=tempascdata&0xff;
                        sumdata = sumdata + ucdata;
                        UartSendchar(ucdata);
                  }
                  UartSendchar(ETX);

末尾还有些打酱油的没添加,不过已经够长了。

sumdata是定义在这个函数里的局部变量,    unsigned char sumdata=0;
if(ReadAddr==0x01c0)
                              Readdat=0x0a; 增加了理解难度,暂时去掉,这样变为
for(i=1;i<=ReadLen;i++)
         {
            Readdat=IAPFlashReadMode(ReadAddr+i-1);   //    读FlashData
            。。。。。
          }
应该是把要读的字节一个一个从Flash中读出。

终于要接触没有学过的IAP了,原则是在这里理解IAP不能喧宾夺主,点到为止。找到定义
// 函数名称: IAPFlashReadMode
//
// 功能描述:单字节读取模式
// 输 入:unsigned int codeaddr
// 定义在IAP.c中         
// 输 出:unsigned char
unsigned char IAPFlashReadMode(unsigned int codeaddr)    // 读模式
{
    ISPCR=0x83;            // ISPCR.7=1,启用ISP
                        // ISPCR=011, 假设MPC82系列运行在11.0592M
    IFMT=0x01;            // 进入读模式
    IFADRH=codeaddr>>8;    // 这个字节必须在IAP存储区
    IFADRL=codeaddr;
    SCMD=0x46;            // 触发的ISP处理
    SCMD=0xb9;            // MCU将会停止运行.直到处理完成    // 触发IAP
    return IFD;            // 读出的数据
}
还需要寄存器信息(reg_mpc82g516.h)
// (ISP)
sfr ISPCR   = 0xE7;
sfr IFMT      = 0xE5;
sfr IFD       = 0xE2;
sfr IFADRH    = 0xE3;
sfr IFADRL    = 0xE4;
sfr SCMD      = 0xE6;
需要再复习一下sfr,sfr 并标准C 语言的关键字,而是Keil 为能直接访问80C51 中的SFR 而提供了一个新 的关键词,其用法是: sfr 变量名=地址值。

不是很理解。把i=1带入,看看结果
Readdat=IAPFlashReadMode(ReadAddr+i-1);

即Readdat=IAPFlashReadMode(ReadAddr);
然后调用IAP函数
    IFADRH=ReadAddr>>8;    // 这个字节必须在IAP存储区
    IFADRL=ReadAddr;
然后
    Readdat=IFD;
不是很理解,可能需要看mpc82g516手册,目前没有精力可分散。暂时勉强理解为读出了所需的数据。

然后集中精力理解后面的代码
                        tempascdata=hextoasc(Readdat);
                        ucdata=tempascdata>>8;
                        sumdata = sumdata + ucdata;
                        UartSendchar(ucdata);
                        ucdata=tempascdata&0xff;
                        sumdata = sumdata + ucdata;
                        UartSendchar(ucdata);




oldbeginner 发表于 2013-11-9 13:46:15

oldbeginner 发表于 2013-11-9 13:08 static/image/common/back.gif
//Read code第三部分,计算 sumdata 并发送读取字节
                  sumdata=0;
               ...

                        tempascdata=hextoasc(Readdat);
                        ucdata=tempascdata>>8;
                        sumdata = sumdata + ucdata;
                        UartSendchar(ucdata);
                        ucdata=tempascdata&0xff;
                        sumdata = sumdata + ucdata;
                        UartSendchar(ucdata);
//========================
// 函数名称: hextoasc
//定义在FX1N.c中
// 功能描述:HEX 转 ASCII
//
// 输 入:unsigned char hexdata
//         
// 输 出:unsigned int
http://www.amobbs.com/data/attachment/forum/201311/09/122021u28lz05f0riirfdc.jpg.thumb.jpg

读法挺怪异的,怪不得代码也不好理解。


而tempascdata是定义在函数内部的一个局部变量,   unsigned inttempascdata=0; 16位的
                        tempascdata=hextoasc(Readdat);
ucdata和sumdata也是一个局部变量,    unsigned char ucdata=0;    unsigned char sumdata=0;
把Readdat转换成ASCII,再看后面的(把UartSendchar顺序提前,增强理解)
                        ucdata=tempascdata>>8;
                        UartSendchar(ucdata);
                        sumdata = sumdata + ucdata;
                     
tempascdata>>8,向右移8位,再发送,和上图协议一致。然后发送低8位
                        ucdata=tempascdata&0xff;
                        UartSendchar(ucdata);
                        sumdata = sumdata + ucdata;
sumdata是检验和。

oldbeginner 发表于 2013-11-9 14:15:56

oldbeginner 发表于 2013-11-9 13:46 static/image/common/back.gif
tempascdata=hextoasc(Readdat);
                        ucdata=tempascdata> ...

第三部分末尾
                  UartSendchar(ETX);
                  sumdata = sumdata + ETX;
                  usum=sumdata&0x0f;
                  tempsum=(usum<10)?(usum+0x30):(usum+0x41-0x0a);
                  sumdata>>=4;
                  usum=sumdata&0x0f;
                  Readdat=(usum<10)?(usum+0x30):(usum+0x41-0x0a);
                  UartSendchar(Readdat);
                  UartSendchar(tempsum);    //    注意此处sum 发送顺序
1、发送ETX,结束符号
               UartSendchar(ETX);
2、校验和继续加
                  sumdata = sumdata + ETX;
3、   usum和tempsum都是局部unsigned char变量,取校验和低4位,然后转换成对应的ASCI保存到tempsum。
                  usum=sumdata&0x0f;
                  tempsum=(usum<10)?(usum+0x30):(usum+0x41-0x0a);

4、取校验和的高4位,然后转换成对应的ASCI保存到Readdat中。
                  sumdata>>=4;
                  usum=sumdata&0x0f;
                  Readdat=(usum<10)?(usum+0x30):(usum+0x41-0x0a);
5、分两次发送检验和
                  UartSendchar(Readdat);
                  UartSendchar(tempsum);    //    注意此处sum 发送顺序

至此,Read code第一遍理解完成,里面还有些细节问题不清楚,不过还是先回到大结构整理一下。

oldbeginner 发表于 2013-11-9 15:18:17

本帖最后由 oldbeginner 于 2013-11-9 15:19 编辑

oldbeginner 发表于 2013-11-9 14:15 static/image/common/back.gif
第三部分末尾
                  UartSendchar(ETX);
                  sumdata = sumdata + ETX;


笔记03主要是找到理解通讯协议和代码的一个入门点,就是Read code功能,理解了这个Read code代码要实现的功能后,再返回到协议当中去。从而对协议和FX1NProcessing有个全局认识。

接下部分将在笔记04中理解:
http://www.amobbs.com/forum.php?mod=viewthread&tid=5558403&pid=7104970&page=1&extra=page%3D1#pid7104970

LJM4U 发表于 2013-11-10 19:17:06

好东西啊

cjc83com 发表于 2013-11-24 10:55:11

刚入门能这么详细分析, 赞一个

yx20016 发表于 2015-6-23 10:18:45

这样的资料太少了,像楼主这样的太少了,顶

limaotaizi 发表于 2016-2-28 23:50:05

厉害,谢谢楼主

不枉此生23 发表于 2016-3-10 21:31:11

楼主有没有plc工控电路原理图啊?

zrh96363 发表于 2016-3-21 09:25:28

简易PLC v1.1.5版
这个没法下载,是屏蔽了吗?
页: [1]
查看完整版本: 开源PLC学习笔记03(再从51开始 通讯)——2013_11_8