首页 > 代码库 > PPI协议(西门子PLCS7-200)
PPI协议(西门子PLCS7-200)
西门子PLC S7-200 PPI协议报文,9600,偶校验,8数据位,1停止位
读取vb100寄存器:
PC发送报文
68 1B 1B 68 2 0 6C 32 1 0 0 0 0 0 E 0 0 4 1 12 A 10 2 0 1 0 1 84 0 3 20 8B 16
PLC返回确认报文:E5
PC发送确认报文:
10 02 00 5C 5E 16
PLC返回数值报文:
68 16 16 68 00 02 08 32 03 00 00 00 00 00 02 00 05 00 00 04 01 FF 04 00 08 08 5E 16
读取vw1000寄存器:
PC发送报文:
68 1B 1B 68 2 0 6C 32 1 0 0 0 0 0 E 0 0 4 1 12 A 10 2 0 1 0 1 84 0 1F 40 C9 16
PLC返回确认报文:E5
PC发送确认报文:10 02 00 5C 5E 16
PLC返回数值报文:
68 17 17 68 00 02 08 32 03 00 00 00 00 00 02 00 06 00 00 04 01 FF 04 00 10 00 08 67 16
读取vw1000寄存器发送报文分析
68 1B 1B 68 2 0 6C 32 1 0 0 0 0 0 E 0 0 4 1 12 A 10 4 0 1 0 1 84 0 1F 40 C9 16
68: SD 开始定界符 固定值68H
1B: LE 报文数据长度 从DA到DU的字节长度
1B: LER 报文数据长度 LE的重复值
68: SD 开始定界符 固定值68H
2: DA 目标地址 PLC地址2
0: SA 源地址 PC地址0
6C: FC 功能码 6CH或7CH
32 1 0 0 0 0: DSAP (7~12字节)目的服务存取点
0 E 0 0 4 1: SSAP (13~18字节)源服务存取点
12 A 10 4 0 1 0 1 84 0 1F 40: KP (19~30字节)核心部分 “12 A 10 命令开始字符”,
C9: FCS 校验和 从DA到DU的校验和的低字节(DU,数据单元,在KP的后面,但是对于读指令无DU。 校验码的计算就是(第5字节到第30字节的加和,然后Mod 256,取余得
低字节))
16: ED 结束定界符 固定值16H
读取VB0:
68 1B 1B 68 2 0 6C 32 1 0 0 0 0 0 E 0 0 4 1 12 A 10 2 0 1 0 1 84 0 0 0 68 16
VB0返回值:
68 16 16 68 00 02 08 32 03 00 00 00 00 00 02 00 05 00 00 04 01 FF 04 00 08 00 56 16
读取VD100:
68 1B 1B 68 2 0 6C 32 1 0 0 0 0 0 E 0 0 4 1 12 A 10 2 0 4 0 1 84 0 03 20 8E 16
VD100返回值:
68 19 19 68 00 02 08 32 03 00 00 00 00 00 02 00 08 00 00 04 01 FF 04 00 20 42 C8 00 00 7B 16
返回值:
读取QB0(读取整个字节,Q区一个字节有8位,可以控制8个回路):
68 1B 1B 68 2 0 6C 32 1 0 0 0 0 0 E 0 0 4 1 12 A 10 2 0 1 0 0 82 0 0 0 65 16
68 16 16 68 00 02 08 32 03 00 00 00 00 00 02 00 05 00 00 04 01 FF 04 00 08 00 56 16
68 1B 1B 68 02 00 6C 32 01 00 00 00 00 00 0E 00 00 04 01 12 0A 10 02 00 01 00 00 82 00 00 00
写入VD104:
(“写字节,第16字节为5”,“写字,第16字节为6”,“写双字,第16字节为8”)
68 23 23 68 02 00 6C 32 01 00 0 0 0 0 E 0 8 5 1 12 0A 10 02 00 4 0 1 84 0 03 40 00 04 00 20 42 C8 00 00 E5 16
68 23 23 68 02 00 7C 32 01 00 00 00 00 00 0E 00 08 05 01 12 0A 10 02 00 04 00 01 84 00 03 40 00 04 00 20 42 C8 00 00 E5 16
返回值(无论写入什么,返回报文都是这个):
68 12 12 68 00 02 08 32 03 00 00 00 00 00 02 00 01 00 00 05 01 FF 47 16
写入QB开关量输出(输出FF,8回路全开):
68 20 20 68 2 0 6C 32 1 0 0 0 0 0 E 0 5 5 1 12 A 10 2 0 1 0 0 82 0 0 0 0 4 0 8 FF 76 16
写入M0.0
68 20 20 68 02 00 6C 32 01 00 00 00 00 00 0E 00 05 05 01 12 0A 10 01 00 01 00 00 83 00 00 00 00 03 00 01 01 70 16
68 20 20 68 02 00 7C 32 01 00 00 00 00 00 0E 00 05 05 01 12 0A 10 01 00 01 00 00 83 00 00 00 00 03 00 01 01 80 16
写入Q0.0(写入1, “0.0偏移量 00 00 00”):
68 20 20 68 02 00 7C 32 01 00 00 00 00 00 0E 00 05 05 01 12 0A 10 01 00 01 00 00 82 00 00 00 00 03 00 01 01 7F 16
写入Q0.0(写入0):
68 20 20 68 02 00 7C 32 01 00 00 00 00 00 0E 00 05 05 01 12 0A 10 01 00 01 00 00 82 00 00 00 00 03 00 01 00 7E 16
68 23 23 68 02 00 7C 32 01 00 00 00 00 00 0E 00 08 05 01 12 0A 10 02 00 04 00 01 84 00 06 A0 00 04 00 20 42 C8 00 00 E5 16
68 23 23 68 02 00 7C 32 01 00 00 00 00 00 0E 00 08 05 01 12 0A 10 02 00 04 00 01 84 00 06 A0 00 04 00 20 40 80 00 00 0E 16
写入Q0.1(写入1,“0.1偏移量 00 00 01”):
68 20 20 68 02 00 7C 32 01 00 00 00 00 00 0E 00 05 05 01 12 0A 10 01 00 01 00 00 82 00 00 01 00 03 00 01 01 80 16
写入Q0.1(写入0,“0.1偏移量 00 00 01”):
68 20 20 68 02 00 7C 32 01 00 00 00 00 00 0E 00 05 05 01 12 0A 10 01 00 01 00 00 82 00 00 01 00 03 00 01 00 7F 16
写入Q0.2(写入1,“0.2偏移量 00 00 02”):
68 20 20 68 02 00 7C 32 01 00 00 00 00 00 0E 00 05 05 01 12 0A 10 01 00 01 00 00 82 00 00 02 00 03 00 01 01 81 16
写入Q0.2(写入0,“0.2偏移量 00 00 02”):
68 20 20 68 02 00 7C 32 01 00 00 00 00 00 0E 00 05 05 01 12 0A 10 01 00 01 00 00 82 00 00 02 00 03 00 01 00 80 16
???)
写Q0.3(写入1,“0.3偏移量 00 00 03”):
68 20 20 68 02 00 7C 32 01 00 00 00 00 00 0E 00 05 05 01 12 0A 10 01 00 01 00 00 82 00 00 03 00 03 00 01 01 82 16
写入Q0.3(写入0,“0.2偏移量 00 00 03”):
68 20 20 68 02 00 7C 32 01 00 00 00 00 00 0E 00 05 05 01 12 0A 10 01 00 01 00 00 82 00 00 03 00 03 00 01 00 81 16
???
写入Q0.5(写入1,“0.3偏移量 00 00 05”):
68 20 20 68 02 00 7C 32 01 00 00 00 00 00 0E 00 05 05 01 12 0A 10 01 00 01 00 00 82 00 00 05 00 03 00 01 01 84 16
写入Q0.4(写入1,“0.4偏移量 00 00 04”):
68 20 20 68 02 00 7C 32 01 00 00 00 00 00 0E 00 05 05 01 12 0A 10 01 00 01 00 00 82 00 00 04 00 03 00 01 01 83 16
???
写入Q0.6(写入1,“0.6偏移量 00 00 06”):
68 20 20 68 02 00 7C 32 01 00 00 00 00 00 0E 00 05 05 01 12 0A 10 01 00 01 00 00 82 00 00 06 00 03 00 01 01 85 16
???
写入Q0.7(写入1,“0.7偏移量 00 00 07”):
68 20 20 68 02 00 7C 32 01 00 00 00 00 00 0E 00 05 05 01 12 0A 10 01 00 01 00 00 82 00 00 07 00 03 00 01 01 86 16
???
写入Q1.3(写入1,“1.3偏移量 00 00 0B”):
68 20 20 68 02 00 7C 32 01 00 00 00 00 00 0E 00 05 05 01 12 0A 10 01 00 01 00 00 82 00 00 0B 00 03 00 01 01 8A 16
???
写入Q1.1(写入1,“1.1偏移量 00 00 09”):
68 20 20 68 02 00 7C 32 01 00 00 00 00 00 0E 00 05 05 01 12 0A 10 01 00 01 00 00 82 00 00 09 00 03 00 01 01 88 16
写入Q1.0(写入1,“1.0偏移量 00 00 08”):
68 20 20 68 02 00 7C 32 01 00 00 00 00 00 0E 00 05 05 01 12 0A 10 01 00 01 00 00 82 00 00 08 00 03 00 01 01 87 16
写入Q1.3(写入1,“1.3偏移量 00 00 0B”):
68 20 20 68 02 00 7C 32 01 00 00 00 00 00 0E 00 05 05 01 12 0A 10 01 00 01 00 00 82 00 00 0B 00 03 00 01 01 8A 16
返回值:E5 68 12 12 68 00 02 08 32 03 00 00 00 00 00 02 00 01 00 00 05 01 FF 47 16
1793
读取VW1040:
68 1B 1B 68 02 00 6C 32 01 00 00 00 00 00 0E 00 00 04 01 12 0A 10 02 00 02 00 01 84 00 20 80 09 16
读取VD100:
68 1B 1B 68 02 00 6C 32 01 00 00 00 00 00 0E 00 00 04 01 12 0A 10 02 00 04 00 01 84 00 03 20 8E 16
C#核心方法:
public byte[] SendPLC(string CommandStr, SerialPort SpPlc)
{
byte[] return_bytes=new byte[25];
byte[] confirm_bytes;
byte[] confirm_send_bytes = {0x10,0x02,0x00,0x5C,0x5E,0x16};
List<byte> commandlist = HexStrTobyte(CommandStr).ToList();
//计算和校验
int sum=0;
for (int i = 4; i < commandlist.Count; i++)
{
sum = sum + commandlist[i];
}
sum = sum % 256;
commandlist.Add(BitConverter.GetBytes(sum)[0]);
commandlist.Add(0x16);
byte[] command = commandlist.ToArray();
sp_PLC.DiscardInBuffer();
sp_PLC.DiscardOutBuffer();
sp_PLC.Write(command, 0, command.Count());
System.Threading.Thread.Sleep(100);
confirm_bytes = new byte[sp_PLC.BytesToRead];
sp_PLC.Read(confirm_bytes, 0, confirm_bytes.Length);
if (confirm_bytes.Length == 1 && confirm_bytes[0] == 0xE5)
{
sp_PLC.Write(confirm_send_bytes, 0, 6);
System.Threading.Thread.Sleep(200);
return_bytes = new byte[sp_PLC.BytesToRead];
sp_PLC.Read(return_bytes, 0, return_bytes.Length);
}
return return_bytes;
}
参考资料:
西 门子 P P I 协议 混合 类型 多单 元报 文协 议剖 析许 广彬 。 ,王 清
Siemens PPI协议分析
大家好:我是山东临沂的XXX,PLC解密网是我的个人网站。由于前段时间的疯狂的研究西门子PPI协议解密之故,所以无心插柳的研究出了较实用的西门子S7-200 PPI协议,今天奉献大家。我们经常要用于上位机、现场设备与S7-200CPU之间的通讯,但是西门子公司没有公布PPI协议的格式,用户如果想使用PPI协议监控,必须购买其监控产品或第三方厂家的组态软件。大家要知道国内的组态王、紫金桥、力控等等组态公司是花了多少钱才得到的PPI的深层协议吗?其实西门子工控产品的超高价垄断掠夺行为已经引起了我们国家及业内人士的抵制和抗议,他们的什么软件都需要授权且对于系统的霸道性是有目共睹的,而且我是深受其害的。^_^我最近弄了个WINCC,装了一个星期还没装上,网友告诉我要重做系统才可以,悲哀啊。。。。。。
这样给用户自主开发就带来了一定的困难,特别是想用VB、VC等语言自行开发,根本没办法接入PLC,要么你大把掏钱给他们。洋为中用,最近在国外网站得到一个串口监视软件,带协议分析的相当不错,你吧!我就是通过此软件的数据监视、分析方法,找出了PPI协议的关键报文格式所在。
其实西门子S7-200 PLC之间或者PLC与PC之间通信有很多种方式:自由口,PPI方式,MPI方式,Profibus方式。使用自由口方式进行编程时,在上位机和PLC中都要编写数据通信程序。使用PPI协议进行通信时,PLC可以不用编程,而且可读写所有数据区,快捷方便。这也是我们之所以要研究、找出PPI协议的源动力!
下面我们就要说说分析的方法了!
西门子的STEP 7 MicroWIN 是用于S7-200系列PLC的开发工具,它使用PC机上的COM口通过一条PC/PPI编程电缆连到PLC的编程口上。这说明,PC实际上是可以通过串口同S7-200 CPU通讯。只是我们不知道通讯协议而已。通过截获PC机串口上的收发数据,对照Step 7软件发出的指令,我们就有可能分析出有关指令的报文和通讯方式;然后,直接通过串口向PLC发送报文,以验证这些指令报文是否正确。本着这一思想,我们采用以下步骤获得这些报文。
首先你这个英文的串口监控软件,英文不好的网友可以用金山快译翻译一下,你必须使用这个软件,因为我先前使用过很多的监控软件,在收发数据很多的情况下都有死机现象,造成数据丢失,容易给我们错误分析。接下来你先打开这个软件,新建、选择端口COM1,然后再将PC/PPI编程电缆接在COM1上,这样,Step7 Micro/Win发给PLC的报文就可以在监视软件上完全裸露的展现在你的面前了。我们按S7-200系统手册设置好串口参数:9600,8,E偶校验,1位停止位。然后设置好Step7软件,使之能与S7-200 CPU正常通讯。从Step7软件中发出一个明确指令,监视软件就能显示这条报文了(用16进制显示,ASCII码的只能看到几个版本号之类的,其他都没有意义)。
我们的破解策略就是通过软件监视的方法,分析PLC内部固有的PPI通讯协议,然后上位机采用VB编程,遵循PPI通讯协议,读写PLC数据,实现人机操作任务。这种通讯方法,与一般的自由通讯协议相比,省略了PLC的通讯程序编写,只需编写上位机的通讯程序资源。S7-200的编程口物理层为RS-485结构,SIEMENS提供MicroWin软件,采用的是PPI(Point to Point)协议,关于232串口转485你可以采用我们网站开发研制的自制PPI电缆,效果倍好哦! 还是自己动手,丰衣足食啊!
不能光说不练啊!下面我们就说说西门子PLC到底是怎么通讯的。
PC与PLC采用主从方式通讯,PC按如下文的格式发读写指令,PLC作出接收正确的响应(返回应答数据E5H或F9H见下文分析),上位机接到此响应则发出确认命令(10 02 5C 5E 16),PLC再返回给上位机相应数据。一般上位机要连接PLC就要先发送如下寻呼数据 10 02 00 49 4B 16 同志们呐!我们可都是有血、有肉、有思想、有灵感的高级动物啊,面对这么多枯燥、无味、复杂、混乱的机器数字你怎么记呢?反正我是记不住啊!(^_^开始洗脑)这时你可以闭上眼睛,安静、静、再静。。。。。。想一想战争时期的战地对讲机通话模式,那么这个指令(10 02 00 49 4B 16)就可以理解为:00呼叫02,听到请回答。 10起始符 02是上位机要联系的下位级的地址站号,就是要找的人 00就是上位级本本身自己的站号 49寻呼指令 16终止符 其中4B为校验码,是这样得来的:02+00+49的最后两位就是校验码,这就是所说的偶校验或称和校验也称余校验,因为取的是余数。计算器在16进制计算时公式(02+00+49)mod 100得出的数就是校验码,你计算一下是不是等于4B啊!其他的所有PPI协议校验都是如此。假如02站号的PLC收到寻呼信号那么会回答: 10 00 02 00 02 16 意思是:报告00 ,02收到,请指示 这样的解释是不是有意思啊!你有更好的解释吗?接下来呢,找到了要寻呼的人PC就是司令啦就可以发号施令了,发号施令后PLC正确接收后就会发送 E5 字符,意思是:“02洞两明白”。其实啊,说到这里PLC只说他明白,他已经明白了上位机PC的指示,但并没有执行命令,那么要怎么他才执行命令呢?就是上位机PC发出确认命令后才执行。这时上位机会发出(10 02 5C 5E 16),意思是:“请立即执行”。然后PLC就干他应当干的工作了啊!原来PLC也不容易啊,怪不得叫下位机呢!
说了这么多乱不乱呐!目的就是要理清上下级关系、主从关系,指令的顺序,用一个好的记忆方法记住枯燥无味的机器码。
读命令分析:一次读一条数据
SD LE LER SD DA SA FC DASP SSAP DU FCS ED
SD:(Start Delimiter)开始定界符(68H)
LE:(Length)报文数据长度
LER:(Repeated Length)重复数据长度
SD: (Start Delimiter)开始定界符(68H)
SA:(Source Address)目标地址,指该地址的值,就是PLC的地址
DA:(Destination Address)本地地址,指该地址的指针,就是上位机自己的地址
FC:(Function Code)功能码,5CH为交替周期触发,6CH为首次信息周期触发,7CH为交替周期触发。
DSAP:(Destination Service Access Point)目的服务存取点
SSAP:(Source Service Access Point)源服务存取点
DU:(Data Unit)数据单元
FCS:(Frame Check Sequence)校验码
ED:(End Delimiter)结束分界符(16H)
报文数据长度和重复数据长度为自DA至DU的数据长度,校验码为DA至DU数据的和校验,只取其中的末字节值关于这个校验码的计算方法同上面说明。
在读写PLC的变量数据中,读数据的功能码为 6CH,写数据的功能码为 7CH。
对于一次读取一个数据,读命令都是33个字节。前面的0—21字节是相同的,为
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
SD | LE | LER | SD | DA | SA | FC |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
开始符 | 长度 | 长度 | 开始符 | 站号 | 源地址 | 功能码 | 协议识别 | 远程控制 | 冗余识别 | 冗余识别 | 协议数据 | 单元参考 | 参数长度 | 参数长度 | 数据长度 | 数据长度 | 04读05写 | 变量地址数 |
|
|
|
68 | 1B | 1B | 68 | 02 | 00 | 6C | 32 | 01 | 00 | 00 | 00 | 00 | 00 | 0E | 00 | 00 | 04 | 01 | 12 | 0A | 10 |
下面我们列表分析读取PLC密码的指令:68 1B 1B 68 02 00 6C32 01 00 00 00 00 00 0E 00 00 04 01 12 0A10 02 00 08 00 00 03 00 05 E0 D2 16
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
|
|
|
|
|
|
|
| DU | FCS | DE |
读取长度 | 数据个数 | 存储器类型 | 偏移量 | 校验码 | 结束符 | |||||
02 | 00 | 08 | 00 | 00 | 03 | 00 | 05 | E0 | D2 | 16 |
因为是PC上发的读PLC数据的命令,SA=00,DA=02,如果有多个站,DA要改成相应的站号。读命令中从DA到DU的长度为1B即27个字节。从22字节开始根据读取数据的类型、位置不同而不同。上表是读不同存储器命令的Byte22—32。
字节 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
功能 | 读取长度 | 数据个数 | 存储器类型 | 偏移量 | 校验码 | 结束符 | |||||
读Q0.1 | 01 | 00 | 01 | 00 | 00 | 82 | 00 | 00 | 00 | 64 | 16 |
读M0.0 | 01 | 00 | 01 | 00 | 00 | 83 | 00 | 00 | 00 | 65 | 16 |
读M0.1 | 01 | 00 | 01 | 00 | 00 | 83 | 00 | 00 | 01 | 66 | 16 |
读SMB34 | 02 | 00 | 01 | 00 | 00 | 05 | 00 | 00 | 01 | F9 | 16 |
读VB100 | 02 | 00 | 01 | 00 | 01 | 84 | 00 | 03 | 20 | 8B | 16 |
读VW100 | 04 | 00 | 01 | 00 | 01 | 84 | 00 | 03 | 20 | 8D | 16 |
读VD100 | 06 | 00 | 01 | 00 | 01 | 84 | 00 | 03 | 20 | 8F | 16 |
读I0.5 | 01 | 00 | 01 | 00 | 00 | 81 | 00 | 00 | 05 | 68 | 16 |
读I0.7 | 01 | 00 | 01 | 00 | 00 | 81 | 00 | 00 | 07 | 6A | 16 |
上表读命令的Byte22-32从表中我们可以得出以下结果:
Byte 22 读取数据的长度
01:1 Bit 02:1 Byte
04:1 Word 06:Double Word
Byte 24数据个数,这里是01 ,一次读多个数据时见下面的说明。
Byte 26 存储器类型,01:V存储器 00:其它
Byte 27 存储器类型
04:S 05:SM 06:AI 07:AQ 1E: C
81:I 82:Q 83:M 84:V 1F: T
Byte 28,29,30存储器偏移量指针(存储器地址*8),如:VB100,存储器地址为100,偏移量指针为800,转换成16进制就是320H,则Byte 28—29这三个字节就是:00 03 20。
Byte 31 校验和,前面已说到这是从(DA+SA+DSAP+SSAP+DU) Mod 256 。
一次读多条数据
对于一次读多个数据的情况,前21Byte与上面相似只是长度LD,LDr及Byte 14不同:
Byte 14 数据块占位字节,它指明数据块占用的字节数。与数据块数量有关,长度=4+数据块数*10,如:一条数据时为4+10=0E(H);同时读M,V,Q三个不同的数据块时为4+3*10=22(H)。
Byte 22 总是02 即以Byte为单位。
Byte 24 以字节为单位,连续读取的字节数。如读2个VD则Byte24=8
Byte 19---30 按上述一次读一个数据的格式依次列出,
Byte 31---42 另一类型的数据,也是按上述格式给出。
以此类推,一次最多读取222个字节的数据。
写命令分析:一次写一个Double Word类型的数据,写命令是40个字节,其余为38个字节。写一个Double Word类型的数据,前面的0—21字节为 :
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
开始符 | 长度 | 长度 | 开始符 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
68 | 21 | 21 | 68 | 02 | 00 | 7C | 32 | 01 | 00 | 00 | 00 | 00 | 00 | 0E | 00 | 00 | 04 | 01 | 12 | 0A | 10 |
68 23 23 68 02 00 6C 32 0100 00 00 00 00 0E 00 00 04 01 12 0A10
写一个其它类型的数据,前面的0—21字节为 :(与上面比较,只是长度字节发生变化)
68 21 21 68 02 00 6C 32 0100 00 00 00 00 0E 00 00 04 01 12 0A10
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
数据长度 | 数据个数 | 存储类型 | 偏移量 | 数据形式 | 数据位数 | 写入值 | 校验码 | 终止符 | |||||||
01 | 00 | 01 | 00 | 00 | 82 | 00 | 00 | 00 | 00 | 03 | 00 | 01 | 01 | 79 | 16 |
从22字节开始根据写入数据的值和位置不同而变化。上表是几个写命令的Byte22—40。
字 节 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 3839 40
写入位置及值长度 个数 类型 偏移量 位数 值、校验码、 结束符
M0.0=1 01 00 01 00 00 83 00 00 00 00 03 00 01 01 00 71 16
M0.0=0 01 00 01 00 00 83 00 00 00 00 03 00 01 00 00 70 16
M0.1=1 01 00 01 00 00 83 00 00 01 00 03 00 01 01 00 72 16
vb100=10 02 00 01 00 01 84 00 03 20 00 04 00 08 10 00 AE 16
vb100=FF 02 00 01 00 01 84 00 03 20 00 04 00 08 FF 00 9D 16
VW100=FFFF 04 00 01 00 01 84 00 03 20 00 04 00 10 FF FF A6 16
VD100=FFFFFFFF 06 00 01 00 01 84 00 03 20 00 04 00 20 FF FF FF FF B8 16
写命令的Byte22—最后, 经分析我们可以得出以下结果:
Byte 22-- Byte 30 写入数据的长度、存储器类型、存储器偏移量与读命令相同。T,C等不能用写命令写入。
Byte 32 如果写入的是位数据这一字节为03,其它则为04
Byte 34 写入数据的位数
01: 1 Bit 08: 1 Byte 10H: 1 Word 20H: 1 Double Word
Byte 35--40值、校验码、结束符
如果写入的是位、字节数据,Byte35就是写入的值,Byte36=00,Byte37=检验码,Byte38=16H,结束。如果写个的是字数据(双字节),Byte35,Byte36就是写入的值, Byte37=检验码,Byte38=16H,结束。如果写个的是双字数据(四字节),Byte35—38就是写入的值, Byte39=检验码,Byte40=16H,结束。
看完上面的指令分析我们现在就举例几个常用的PPI协议来分析一下:
PC寻呼:10 02 00 49 4B 16
PLC返回:10 00 02 02 04 16
PC发送:10 02 00 5C5E 16
PLC返回: E5
我们先来看看西门子S7-200PLC的读取密码指令:
请用串口软件以16进制发送,端口设置9600;e;8;1
发送:68 1B 1B 68 02 00 6C 32 01 00 00 00 00 00 0E 00 00 04 01 120A 10 02 00 08 00 00 0300 05 E0 D2 16 意思:要求传送系统存储区05E0位开始的8个字符(这就是8个密码数值)。
如果通讯无误,PLC会返回 E5,意思:已经收到
那么这时上位机再次发送指令 10 02 00 5C 5E 16 意思:请执行命令。(说到这里打住一下,PLC返回E5指令后上位机PC要在很短的时间内发送确认指令,晚了刚才的指令就无效了具体多长时间我也没测准,反正1、2秒时间是没有问题的。)那么这时PLC就真的执行命令了返回如下字符:68 1D 1D 68 00 02 08 32 03 00 00 00 00 00 02 00 0C 00 00 04 01 FF 04 00 40 9B 98 02 06 9D9A 00 76 7D 16
好了,说到这里就此停止,大家看看密码是多少啊!你如果真正明白了PPI协议就不难找出出密码了,但是这个密码是经过二次加密的,并不是真正的密码,还需要破译,至于密码算法在此不便公开,不过你多做实验一定能得出结果的。
下面再看一个读取PLC版本号的指令:
我们在解密中首先要确定的是PLC的版本号。就是要看看是老版本还是02版的,也好做出加解密方案。他的通讯源码是这样的:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 2930 31 32
68 1B 1B 68 02 00 7C 32 0100 00 00 00 00 0E 00 00 04 01 12 0A10 02 00 14 00 00 03 00 00 00 09 16
发送完上面数据PLC返回E5.
再次发送确认指令:10 02 00 5C5E 16
这时plc的版本号就返回来了。看下面:
68 29 29 68 00 02 08 32 03 00 00 00 00 00 02 00 18 00 00 04 01 FF 04 00 A0 43 50 55 20 32 32 36 20 43 4E 20 2020 20 20 20 30 32 30 31 D7 16
你看这一段:43 50 55 20 32 32 36 20 43 4E 20 20 20 20 20 2030 32 30 31 就是plc版本号的ASCII码。用ASC方式显示就会看的更明白上面数据是:C P U SP 2 2 6 SP C N 02 0 1 (sp就是空格)
再一个就是读TD200密码指令:
68 1B 1B 68 02 00 6C 32 01 00 00 00 00 00 0E 00 00 04 01 12 0A 10 02 00 02 00 01 84 00 00 50 B9 16 (VW10)
写M0指令
68 20 20 68 02 00 7C 32 01 00 00 00 00 00 0E 00 05 05 01 12 0A10 01 00 01 00 00 83 00 00 00 00 03 00 01 01 8016
读222位3区(系统区)数据指令
68 1B 1B 68 02 00 6C 32 01 00 00 00 00 00 0E 00 00 04 01 12 0A 10 02 00 DE 00 00 03 00 00 00 C3 16
读取密码保护位指令
68 1B 1B 68 02 00 6C 32 01 00 00 00 00 00 0E 00 00 04 01 12 0A 10 02 00 01 00 00 03 00 05 D8 C3 16
改写密码保护位指令(你来验证是否可行)
68 20 20 68 02 00 7C 32 0100 00 00 00 00 0E 00 05 05 01 12 0A10 08 00 01 00 00 03 00 05 D8 00 04 00 08 04 EF 16
68 20 20 68 02 00 7C 32 0100 00 00 00 00 0E 00 05 05 01 12 0A10 02 00 01 00 00 03 00 05 D8 00 03 00 08 04 E8 16
全部清除指令:
68 21 21 68 02 00 7C 32 07 00 00 00 24 00 08 00 0C 00 01 12 04 11 45 01 00 FF 09 00 08 16 19 06 0D 0108 18 1E EE 16
块代码:08 程序块 0A数据块 0B系统块
写指令:先发10 02 00 5C 5E 16 后发写指令
1、写一位M区(例子M0.0)
先发10 02 00 5C 5E 16 收到E5后
发 68 20 20 68 02 00 7C32 01 00 00 00 00 00 0E 00 05 05 01 12 0A 10 01 00 01 00 00 83 00 00 00 00 03 00 01 0180 16
收到E5 说明写入完成(只要报文长度,跟校验码对了,就会回复E5)
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
开始符 | 报文长度 | 报文长度 | 开始符 | 目标 地址(PLC ) | 源站地址(上位机) | 功能码 |
|
|
|
|
|
|
|
|
|
| 写 |
|
|
|
|
68 | 20 | 20 | 68 | 02 | 00 | 7C | 32 | 01 | 00 | 00 | 00 | 00 | 00 | 0E | 00 | 05 | 05 | 01 | 12 | 0A | 10 |
报文长度为: 目标地址 到 倒数第二位的校验位前面 的字节长度(根据写入值不通报文长度不同)
功能码:7C表示写入;6C表示读取。
17位:05表示写入;04表示读取。
16位: 16位的05表示写入的是 位或者字节(即用一个字节存储)
06 表示 字; 08表示双字(4个字节); 0C表示8个字节
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
数据长度 | 数据个数 | 存储类型 | 偏移量 | 数据形式 | 数据位数 | 写入值 | 校验码 | 终止符 | |||||||
01 | 00 | 01 | 00 | 00 | 82 | 00 | 00 | 00 | 00 | 03 | 00 | 01 | 01 | 79 | 16 |
数据长度(22位):01位;02字节;04字;06双字。
数据个数:0100表示一个;0200表示连续的两个;0400连续的4个。
存储类型:26位:01 V区;00其他。
27位:04 S区;05 SM区;06 AI;07 AQ;1E C;
81 I;82 Q;83 M;84 V ;1F T
偏移量: 0000 0000 0000 0000 0000 0XXX (XXX表示位)
例如:10.3=101 0.011 即00 00 53
数据形式:03表示位;04表示其他。
数据位数:即写入数据多少位。01一位;08八位;10十六位;20三十二位。
写入值:写入位,字节均用一个字节存储;写入双字得用四个字节。
校验码:即报文的偶校验(所有之和Mod 100H)
终止符:16H
若M10.3=1写入, 00 00 53 01 D3 (校验码D3是从开头第五个02到倒数第三个01的所有数字的偶校验(算术和))
因为 0101 0.011(10.3)为00 00 53
即:68 20 20 68 02 00 7C 32 01 00 00 00 00 00 0E 00 05 05 01 120A 10 01 00 01 00 00 83 00 00 53 00 03 00 01 01D3 16
M10.3=0
即:68 20 20 68 02 00 7C 32 01 00 00 00 00 00 0E 00 05 05 01 120A 10 01 00 01 00 00 83 00 00 53 00 03 00 01 00D2 16
2、写V区一个字节(例子VB100=10H)
先发10 02 00 5C 5E 16 收到E5后
发:68 20 20 68 02 00 7C 32 0100 00 00 00 00 0E 00 05 05 01 12 0A10 02 00 01 00 01 84 00 03 20 00 04 00 08 10 bd16
3、写V区一个字(双字)
发:68 21 21 68 02 00 7C32 01 00 00 00 00 00 0E 00 06 05 01 12 0A 10 04 00 01 00 01 84 00 03 20 00 04 0010 ab cd 30 16
发:68 23 23 68 02 00 7C32 01 00 00 00 00 00 0E 00 08 05 01 12 0A 10 06 00 01 00 01 84 00 03 20 00 04 0020 ab cd ef fe 31 16
读指令:先发读指令,后发10 02 00 5C 5E 16
1、读取数据(例子读取VW10的值,值为FFFF)
先发读取命令:
68 1B 1B 68 02 00 6C 32 01 00 00 00 00 00 0E 00 00 04 01 12 0A 10 02 00 02 0001 84 00 00 50 B9 16 (红色或者改为04 00 01 00 校验也得改)
回复 E5
然后发送 10 02 00 5C 5E 16
收到数据:
68 17 17 68 00 02 08 32 03 00 0000 00 00 02 00 0600 00 04 01 FF 04 00 10 FF FF 5D 16
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
|
|
|
|
|
|
|
| DU | FCS | DE |
读取长度 | 数据个数 | 存储器类型 | 偏移量 | 校验码 | 结束符 | |||||
02 | 00 | 08 | 00 | 00 | 03 | 00 | 05 | E0 | D2 | 16 |
读指令的前21位都是相同的。读取长度,数据个数,存储类型,偏移量都与写指令里面的定义一样。
收到数据中的
16位:05表示收到数据用一个字节存储,可能是PLC位,也可能是一个字节;06表示用两个字节存储,即一个字;08双字;0C表示8个字节。
22位:数据形式,03表示位;04表示其他。(主要针对C,T可能是位也可能是计数值,计数时间)
24位:表示读取数值的位数。01表示一位;08八位;10十六位;20三十二位。
25位之后几位:要读取的数值。
若回复F9(会产生不同结果,但接受值不变)
然后发送 10 02 00 5C 5E 16
收到数据:(关闭串口再打开的不同结果)
DA A1 21 1B 90 32 61 66 40 40 8000 00 00 02 00 06 00 00 04 01 FF 0400 10 FF FF 5D 16
B4 A1 21 1B 10 30 61 66 40 80 80 00 00 00 02 00 06 00 00 04 01 FF 04 00 10 FF FF 5D 16
68 17 17 68 00 02 08 32 03 00 0000 00 00 02 00 06 00 00 04 01 FF 0400 10 FF FF 5D 16
4、读取VB10
68 1B 1B 68 02 00 6C 32 01 00 00 00 00 00 0E 00 00 04 01 120A 10 02 00 01 00 01 84 00 00 50 B8 16
68 16 16 68 00 02 08 32 03 00 0000 00 00 02 00 05 00 00 04 01 FF 0400 08 FF 55 16
5、读取VD10
68 1B 1B 68 02 00 6C 32 01 00 00 00 00 00 0E 00 00 04 01 120A 10 06 00 01 00 01 84 00 00 50 BC 16
68 19 19 68 00 02 08 32 03 00 0000 00 00 02 00 08 00 00 04 01 FF 0400 20 FF FF 00 00 6F 16
6、读取VB10后面的8个字节
68 1B 1B 68 02 00 6C 32 01 00 00 00 00 00 0E 00 00 04 01 120A 10 02 00 08 00 01 8400 00 50 Bf 16
68 1D 1D 68 00 02 08 32 03 00 0000 00 00 02 00 0C 00 00 04 01 FF 04 00 40 FF FF 00 00 00 00 00 00 93 16
7、读取V10.0
68 1B 1B 68 02 00 6C 32 01 00 00 00 00 00 0E 00 00 04 01 120A 10 01 00 01 00 01 8400 00 50 B7 16
68 16 16 68 00 02 08 32 03 00 0000 00 00 02 00 05 00 00 04 01 FF 03 00 01 01 4F 16
4、读取Q0.1
发送:68 1B 1B 68 02 00 6C 32 01 00 00 00 00 00 0E 00 00 04 01 120A 10 01 00 01 00 00 82 00 00 01 65 16
回复:E5
然后发送 10 02 00 5C 5E 16
收到数据:
68 16 16 68 00 02 08 32 03 00 0000 00 00 02 00 05 00 00 04 01 FF 03 00 01 004E 16 Q0.1为0时
68 16 16 68 00 02 08 32 03 00 0000 00 00 02 00 05 00 00 04 01 FF 03 00 0101 4F 16 Q0.1为1时
5、读取Q1.3
发送:68 1B 1B 68 02 00 6C 32 01 00 00 00 00 00 0E 00 00 04 01 120A 10 01 00 01 00 00 82 00 00 0B 65 16
68 16 16 68 00 02 08 32 03 00 0000 00 00 02 00 05 00 00 04 01 FF 03 00 01 004E 16
6、读取M0.0
发送:68 1B 1B 68 02 00 6C 32 01 00 00 00 00 00 0E 00 00 04 01 120A 10 01 00 01 00 00 8300 00 00 65 16
68 16 16 68 00 02 08 32 03 00 0000 00 00 02 00 05 00 00 04 01 FF 03 00 01 00 4E 16
VB参考代码:
初始化程序
Private Sub Form_Load()
With MSComm1
.CommPort = 1
.Settings = "9600,e,8,1"
‘
波特率
9600
,偶校验,
8
数据位,
1
停止位
.InputLen = 0
.RThreshold = 1
‘
收到字符就产生事件
.InputMode = comInputModeBinary
‘
二进制接收模式
End With
If MSComm1.PortOpen = False Then
MSComm1.PortOpen = True
‘
打开通讯串口
End If
End Sub
Private Sub Form_Unload(Cancel As Integer)
If MSComm1.PortOpen = True Then
MSComm1.PortOpen = False
‘
关闭通讯串口
End If
End Sub
5.2
读
VW
函数
Function ppird(readaddress As String)
‘
读
VW
的地址,例如:
1000
Dim i As Integer, temp As Integer
Dim sendbyte(32) As Byte
sendbyte(0) = &H68
sendbyte(1) = &H1B
sendbyte(2) = &H1B
sendbyte(3) = &H68
sendbyte(4) = &H2
sendbyte(5) = &H0
sendbyte(6) = &H6C
sendbyte(7) = &H32
sendbyte(8) = &H1
sendbyte(9) = &H0
sendbyte(10) = &H0
sendbyte(11) = &H0
sendbyte(12) = &H0
sendbyte(13) = &H0
sendbyte(14) = &HE
sendbyte(15) = &H0
sendbyte(16) = &H0
sendbyte(17) = &H4
sendbyte(18) = &H1
sendbyte(19) = &H12
sendbyte(20) = &HA
sendbyte(21) = &H10
sendbyte(22) = &H4
sendbyte(23) = &H0
sendbyte(24) = &H1
sendbyte(25) = &H0
sendbyte(26) = &H1
sendbyte(27) = &H84
sendbyte(28) = &H0
sendbyte(29) = (readaddress * 8) \ 256
‘
计算读高位地址
sendbyte(30) = (readaddress * 8) Mod 256
‘
计算读低位地址
sendbyte(31) = &H0
sendbyte(32) = &H16
For i = 4 To 30
temp = temp + sendbyte(i)
Next
sendbyte(31) = temp Mod 256
‘
计算校验码
ppird = sendbyte
‘‘
返回读
VW
指令码
End Function
5.3
写
VW
函数
Function ppiww(adressx As Integer, datax As Integer)
‘adressx
为写
VW
的地址
datax
要写入
VW
的整数
Dim i As Integer, temp As Integer
Dim sendbyte(38) As Byte
sendbyte(0) = &H68
sendbyte(1) = &H21
sendbyte(2) = &H21
sendbyte(3) = &H68
sendbyte(4) = &H2
sendbyte(5) = &H0
sendbyte(6) = &H6C
sendbyte(7) = &H32
sendbyte(8) = &H1
sendbyte(9) = &H0
sendbyte(10) = &H0
sendbyte(11) = &H0
sendbyte(12) = &H0
sendbyte(13) = &H0
sendbyte(14) = &HE
sendbyte(15) = &H0
sendbyte(16) = &H6
sendbyte(17) = &H5
sendbyte(18) = &H1
sendbyte(19) = &H12
sendbyte(20) = &HA
sendbyte(21) = &H10
sendbyte(22) = &H4
sendbyte(23) = &H0
sendbyte(24) = &H1
sendbyte(25) = &H0
sendbyte(26) = &H1
sendbyte(27) = &H84
sendbyte(28) = &H0
sendbyte(29) = (adressx * 8) \ 256
sendbyte(30) = (adressx * 8) Mod 256
sendbyte(31) = &H0
sendbyte(32) = &H4
sendbyte(33) = &H0
sendbyte(34) = &H10
sendbyte(35) = datax \ 256
sendbyte(36) = datax Mod 256
sendbyte(37) = &H0
sendbyte(38) = &H16
For i = 4 To 36
temp = temp + sendbyte(i)
Next
sendbyte(37) = temp Mod 256
ppiww = sendbyte
‘
返回写
VW
指令码
End Function
5.4
利用
MSCOMM
的事件获取数据
Private Sub MSComm1_OnComm()
Dim rcvtemp(5) As Byte
‘
定义确认发送的数组数据元素为字节
Dim rcv_array() As Byte
Dim yy As Long
Select Case MSComm1.CommEvent
Case comEvReceive
rcv_array = MSComm1.Input
‘
取出串口接收缓冲器的数据。
If rcv_array(0) = &HE5 Then
SComm1.RThreshold =25
rcvtemp(0) = &H10
rcvtemp(1) = &H2
rcvtemp(2) = &H0
rcvtemp(3) = &H5C
rcvtemp(4) = &H5E
rcvtemp(5) = &H16
MSComm1.Output = rcvtemp
‘
发送确认指令码
ElseIF rcv_array(0) = &H68 AND rcv_array(1) = &H17 THEN
yy = Val(rcv_array(25)) * 256 + Val(rcv_array(26))
‘
计算读取数据
Text1 = yy
ELSE
MSComm1.RThreshold = 1
End If
End Select
watchtimer.Enabled = False
End Sub