PLC/PAC
串行通信和并行通信是两种不同的数据传输方式。
串行通信就是通过一对导线将发送方与接收方进行连接,传输数据的每个二进制位,按照规定顺序在同一导线上依次发送与接收。例如,常用的优盘USB接口就是串行通信。串行通信的特点是通信控制复杂,通信电缆少,因此与并行通信相比,成本低。
并行通信就是将一个8位数据(或16位、32位)的每一个二进制位采用单独的导线进行传输,并将传送方和接收方进行并行连接,一个数据的各二进制位可以在同一时间内一次传送。例如,老式打印机的打印口和计算机的通信就是并行通信。并行通信的特点是一个周期里可以一次传输多位数据,其连线的电缆多,因此长距离传送时成本高。
记得两年前刚开始从事软件开发工作时,第一份任务就是开发一个程序能够实现与三菱PLC 串口通信。所谓通信,其实质主要是对PLC 的D寄存器(dword)读写操作。但是因为日本为了保护其产品,并不开发串口通信协议。在不开发通信协议的情况,如果想实现通信,首先需要做的便是通过数据分析,破解其通信协议。这里就不讲解如何破解了,主要是介绍下当时博主开发程序的背景。
小编写这篇文章的主要目的是为了分享过去自己的开发经验,因为自己在开发的过程中曾经接受过很多开源软件的帮助,现在这是转入正题。
涉及字节流数据通信,必然要涉及通信协议。鉴于当时的开发需求,博主仅对D寄存器的读写协议分析过。其他寄存器理论上是相似,有兴趣的同学可以自行分析数据进行测试。
D寄存器的通信协议相对比较简单,主要可以分为:
1.问候应答协议
2.状态查询协议
3.状态配置协议
4.数据反馈协议
在PLC通信过程中主要的三个难点在于寄存器的加密解密,数据信息加密和解密,以及字符的校验。
《span style=“font-size:18px;”》void PLC_dataparse::Encrypt_toPLCaddress( BYTE *parray , const UINT paddress )
{
int encode_address = 0x1000 + paddress * 2;
BYTE encrypt_key = encode_address & 0xf;
parray[3] = (encrypt_key《10) ? (encrypt_key + 0x30) : (encrypt_key + 0x41 - 0xa);
encrypt_key = (encode_address 》》 4) & 0xf;
parray[2] = (encrypt_key《10) ? (encrypt_key + 0x30) : (encrypt_key + 0x41 - 0xa);
encrypt_key = (encode_address 》》 8) & 0xf;
parray[1] = (encrypt_key《10) ? (encrypt_key + 0x30) : (encrypt_key + 0x41 - 0xa);
encrypt_key = (encode_address 》》 12) & 0xf;
parray[0] = (encrypt_key《10) ? (encrypt_key + 0x30) : (encrypt_key + 0x41 - 0xa);
}
《/span》
《span style=“font-size:18px;”》void PLC_dataparse::Encrypt_toPLCcontent( BYTE * parray , const UINT pcontent )
{
BYTE encrypt_key = pcontent & 0xf;
parray[1] = (encrypt_key《10) ? (encrypt_key + 0x30) : (encrypt_key + 0x41 - 0xa);
encrypt_key = (pcontent 》》 4) & 0xf;
parray[0] = (encrypt_key《10) ? (encrypt_key + 0x30) : (encrypt_key + 0x41 - 0xa);
encrypt_key = (pcontent 》》 8) & 0xf;
parray[3] = (encrypt_key《10) ? (encrypt_key + 0x30) : (encrypt_key + 0x41 - 0xa);
encrypt_key = (pcontent 》》 12) & 0xf;
parray[2] = (encrypt_key《10) ? (encrypt_key + 0x30) : (encrypt_key + 0x41 - 0xa);
}
《/span》
《span style=“font-size:18px;”》void PLC_dataparse::Add_checkcode( BYTE * pdest , BYTE * psrc , const UINT plenth )
{
int sumtemp = 0;
for ( unsigned int i = 0; i《 plenth; i++)
{
sumtemp += (*(psrc + i));
}
BYTE encrypt_key = sumtemp & 0xf; // get low 4 bit
pdest[1] = (encrypt_key《10) ? (encrypt_key + 0x30) : (encrypt_key + 0x41 - 0xa);
encrypt_key = (sumtemp 》》 4) & 0xf; // get high 4 bit
pdest[0] = (encrypt_key《10) ? (encrypt_key + 0x30) : (encrypt_key + 0x41 - 0xa);
}
《/span》
《span style=“font-size:18px;”》double PLC_dataparse::Get_content( BYTE *parray , UINT plenth )
{
BYTE dl_data[4];
BYTE pre_data[4];
double pow_numb;
for (int j = 0; j《4; j++) //剔除杂码
{
pre_data[j] = parray[j + 1];
}
//////////////////////////////////////////////////////////////////////////
dl_data[1] = (pre_data[0]《0x40) ? (pre_data[0] - 0x30) : (pre_data[0] - 0x41 + 0x0a);
dl_data[0] = (pre_data[1]《0x40) ? (pre_data[1] - 0x30) : (pre_data[1] - 0x41 + 0x0a);
dl_data[3] = (pre_data[2]《0x40) ? (pre_data[2] - 0x30) : (pre_data[2] - 0x41 + 0x0a);
dl_data[2] = (pre_data[3]《0x40) ? (pre_data[3] - 0x30) : (pre_data[3] - 0x41 + 0x0a);
for (int i = 0; i《4; i++)
{
dl_data[i] = dl_data[i] & 0xf;
}
pow_numb = dl_data[3] * pow(16.0, 3.0) + dl_data[2] * pow(16.0, 2.0) + dl_data[1] * 16 + dl_data[0];
return pow_numb;
}
《/span》
int PLC_dataparse::Check_checkcode( BYTE *parray , UINT plenth )
{
int error_code = PLC_SUCCESS;
const int legal_lenth = 8; //the define legal lenth
if (plenth != legal_lenth)
{
error_code = PLC_CRCERROR;
return error_code;
}
//////////////////////////////////////////////////////////////////////////
//check code
else
{
BYTE *pbyte = new BYTE[2];
// split out head mark , tail check out
Add_checkcode(&pbyte[0], &parray[1], plenth - 3); //calculate the check code
for (int j = 0; j《2; j++)
{
if (pbyte[j] != parray[plenth - 2 + j])
{
error_code = PLC_CRCERROR;
break;
}
}
// release the pointer and it‘s stack
delete pbyte;
pbyte = NULL;
return error_code;
}
}
上述代码是使用PLC窗口通信的最大的难点。一旦掌握几大难点,基本PLC的串口通信就很简单了。
另附上一份当时自己开发的三菱PLCD寄存器调试程序。
备注:该调试工具仅支持xp系统
串行通信是指外设和计算机间使用一根数据信号线一位一位地传输数据,每一位数据都占据一个固定的时间长度。“串行”是指外设与接口电路之间的信息传送方式,CPU与接口之间仍按并行方式工作。串行通信的四个重要参数:波特率(衡量通信速度的参数)、奇偶校验位(一种简单的检错方式)、数据位(衡量通信中实际数据位的参数)和停止位(表示单个数据包的最后一位)。
(1)三菱FX2N系列通信数据帧格式
FX2N系列的PLC与计算机之间的通信采用RS-232C标准,其传输速率一般设为9 600 bps,实际传输过程还可设其它,比如115 200 bps等。奇偶校验位采用偶校验。数据以帧为单位发送和接收。一个多字符帧由起始字元、命令号码、元件首地址、结束字元、和校验五部分组成,其中和校验值是将命令码STX—ETX之间的字符的ASCII码(十六进制数)相加,取得所得和的最低二位数。STX和ETX分别表示该字符帧的起始标志和结束标志。
起始字元(STX):ASCII码的起始字元STX对应的16进制数位0x02。无论命令信息还是回应信息,它们的起始字元均为STX,接收方以此来判知传输资料的开始;
命令号码:为两位16进制数。所谓命令号码是指上位机要求下位机所执行的动作类别,例如要求读取或写入单点状态、写入或读取暂存器资料、强制设定、运行、停止等。在回应信息中,下位机会将上位机接收到的命令号码随同其它信息一同发送给上位机;
元件首地址:对应要操作的元件的相应的地址。如从D123单元中读取数据时,要把它对应的地址:0x10F6发送给PLC;
元件个数:一次读取位元件或字元件的数量;
结束字元(ETX):ASCII码的结束字元ETX对应的16进制数为0x03。无论命令信息还是回应信息,它们的结束字元均为ETX,接收方以此来判知此次通讯已结束;
校验码(Checksum):校验码是将STX—ETX之间的ASCII字元的16进制数值以“LRC(Longitudinal Redundancy Check)”法计算出1个Byte长度(两个16进制数值00-FFH)的校验码。当下位机接收到信息后,用同样的方法计算出接收信息的校验码,如果两个校验码相同,则说明传送正确。
(2)三菱FX2N系列通信命令
FX2N系列PLC有4个通信命令,它们是读命令(30H)、写命令(31 H)、强制通命令(37H)、强制断命令(38H)。
(3)三菱FX2N系列通信控制字符
ENQ(ASCII代码05H):计算机向PLC发送请求;
ACK(ASCII代码06H):PLC对ENQ的确定回答;NAK(ASCII代码15H):PLC对ENQ的否认回答;
STX(ASCII代码02H):报文开始;
ETX(ASCII代码02H):报文结束。
(4)FX2N系列设备地址
①读写时的软设备地址
S0-S7:0000H;X0-X7:0080H;Y0-Y7:00AOH;TO-T7:00COH;M0-M7:0100H;CO-C7:01COH;DO-D7:1000H
②置位/复位时的软设备地址
S0-S7:0000H;X0-X7:0400H;Y0-Y7:0500H;TO-T7:0600H:MO-M7: 0800H;CO-C7:0E00H;DO-D7:0100H
③传输过程
PC机与FX系列PLC之间采用应答方式通信,传输出错则组织重发。其传输过程如图1所示。
PLC根据PC机的命令,在每个循环扫描结束处的END语句后组织自动应答,无需用户在PLC一方编写程序。
系统主要实现PLC与计算机的通讯,具体主要完成PC机指令下传、监测PLC状态、接收PLC信息等功 能。系统组成:小型PLC一台、RS232串口、编程电缆、通讯界面。主操作界面在完成系统功能的前提下,力求明了直观,操作简单灵活方便。系统以VC++6.0为平台,设计的界面如图2所示。
本程序设计了四个串口可供选择,只有在选择串口之后才可进行“打开串口,关闭串口”的操作,当打开串口以后,就可以对PLC进行相应的操作了,为了使界面整洁干净,特别设计了“清空发送区”和“清空接收区”选项,当发送数据和接收数据放满编辑框时只需点击这两个按钮,数据就会清空。且实现代码相当简单,m_sSend.Empty()、m_sReceive.Empty()就可轻松实现这一任务。
PC机与PLC的通讯程序流程图如图3所示。
系统通信控制程序采用了MSComm控件。此控件提供了两种通信方法:①文件驱动,即用MSComm控件的OnComm文件捕获并处理通信事件和错误,它是处理串行端口交互作用的一种非常有效的方法;②查询方式,通过查询串口属性来获得事件和错误,实质上还是属于事件驱动,但在有些情况下显得更为便捷。MSComm6.0控件的属性:①CommPort,设置或返回通信端口号;②Settings,以字符串的形式设置或返回波特率、奇偶校验、数据位和停止位;③PortOpen,设置或返回通信端口的状态,也可以打开和关闭端口;④Input,返回和删除接收缓冲区中的字符;⑤InputMode,设置或返回Input属性取回的数据的类型,数据取回的形式为字符串或二进制数据的数组;⑥CommEvent返回最近的通信事件或错误的数字代码,通信程序设计时可以根据该属性值执行不同的操作,在运行时为只写;⑦Output,将字符串写入发送缓冲区。
MSComm6.0控件只有一个事件,即Oncomm事件。在通信时如果发生错误或者事件,将会引发Oneomm事件并且改变其属性值,通过GetCommEvent()可获得Oncomm产生事件或错误的代码。在与PLC进行通信的过程中,使用MSComm6.0控件可以自动完成PLC对计算机发送信息的接收,最终实现PC机对PLC的状态检测。
软件实现过程:FX2N系列的PLC与计算机之间的通信采用RS-232C标准,其传输速率固定为9 600bps,奇偶校验位采用偶校验。数据以帧为单位发送和接收。PC机向PLC中写数据时首先需对串口进行初始化,并对波特率、校验位等进行设置,然后根据通信协议对PLC进行相应的读写、复位、置位等操作,PLC根据PC机送来的控制字进行相应的操作。数据发送,采用专用发送指令XMT TABLE,CommPort,其中TABLE为发送缓冲区的首地址,首地址中保存要发送的字节数,即数据长度,最大为255,其后的地址中保存要发送的数据,CommPort指定用于发送的端口。对于数据接收,使用接收指令RCV TABLE,CommPort,接收指令激活初始化或结束接收信息,通过制定端口接收信息并存储于数据缓冲区中,数据缓冲区的第一个数据指明了接收的字节数。
将计算机用通讯电缆与PLC相连后,首先发送请求05H以后,验证计算机与PLC是否可以正常通信,接收区显示06,表示PLC对ENQ的确定回答,即PLC已准备好,可以进行下面的操作,具体如图4和图5。
这里主要对PLC读值功能进行验证。读操作命令格式如下:
STX—CMD0一数据段一ETX—SUMH—SUML
在按上述命令格式发送相应的代码后,就可直接读取PLC响应的信息了。响应信息格式如下:
STX—DATA—ETX—SUMH—SUML
图6和图7分别是对PLC进行读值验证时发送数据和接收数据的显示。
其中接收数据显示中的023030033633,是对x软地址值(0080H)读取后接收到的数据。具体算法如下:
nSUMLx=(0X30+0X30+0X03)%16=3《9,
nSUMHx=((0X30+0X30+0X03)%256)/16=6《9,
nSUMLx=0x30+nSUMLYl=0X33,
nSUMHx=0X30+nSUMHY2=0X36
故,转变成两字节ASCII代码SUMLx=33;SUMHx=36。
理论分析和实际操作的结果是一致的,即证明了本设计是准确无误的。
全部0条评论
快来发表一下你的评论吧 !