开源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上。
在图片所显示的帖子中,PC和PLC通讯的过程被展示出来。如果不是代码太长,而且过程复杂,就是一个普通的报文程序,所以看看能否让代码更简洁些,这样更容易理解。
以上就是已知的格式,看是否能简化。
本帖最后由 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 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功能,下一步要查看通讯协议确认一下。
太强了,支持触摸屏不? 本帖最后由 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-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-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: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 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: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 好东西啊 刚入门能这么详细分析, 赞一个 这样的资料太少了,像楼主这样的太少了,顶 厉害,谢谢楼主 楼主有没有plc工控电路原理图啊? 简易PLC v1.1.5版
这个没法下载,是屏蔽了吗?
页:
[1]