第一次技术贴。关于s7-200Modbus从站的上位机软件设计。

已锁定

葫芦娃

  • 帖子

    157
  • 精华

    2
  • 被关注

    9

论坛等级:侠士

注册时间:2010-07-22

普通 普通 如何晋级?

第一次技术贴。关于s7-200Modbus从站的上位机软件设计。

1178

0

2012-02-23 18:24:58

新手,欢迎拍砖,欢迎扫射。
-------------------------
此贴的例子为了实现S7-200通过modbus与上位机通信。软件环境在.NET,语言用C#。
因为这样比组态王wincc等软件更灵活,界面自己定制,简洁。尤其在数据库方面更加完善可靠。
将S7-200配置为modbus从站。
添加MBUS_INIT:Mode=1,启用modbus协议。Addr=2,配置地址。Baud=9600,配置波特率。Parity=0,无奇偶校验。Delay=0,无延时。MaxIQ=128,MaxAI=32,MaxHold=1000。HoldSt~=&VB0,V存储器中保持寄存器的起始地址为VB0的物理地址。别忘了为库存储区分配地址。
然后让VD0=0,让它递增,在上位机中读取此数值。
发送指令为02 03 00 00 00 02 C4 38;02站。功能码为03,读取保持寄存器。起始地址为0000,因为读的是VD0,而将HoldSt~=&VB0,所以起始地址还是0。02是读取2个寄存器。C438为CRC校验码。向PLC发送此帧数据,会收到02 03 04 xx xx yy yy。04为4个字节的数据,x为数据位,y为校验位。
在上位机软件中要提取数据位,将此转化为十进制数据。
(此例用于读取寄存器,还有其他功能。比如02 05 00 01 FF 00 DD C9,写单个线圈指令,将Q0.1置1)
--------------------------
窗体上只有一个按钮,一个文本框。
按钮用于 打开和关闭 发送数据。文本框用于显示接受到的数据。先建立一个modbus
,有CRC校验函数等。模块化程序。
CRC16是检验函数,输入为data数组,输出为2个字节的数组。
class modbus
{
public byte[] CRC16(byte[] data)
{
byte CRC16Lo;
byte CRC16Hi; //CRC寄存器
byte CL; byte CH; //多项式码&HA001
byte SaveHi; byte SaveLo;
byte[] tmpData;

int Flag;
CRC16Lo = 0xFF;
CRC16Hi = 0xFF;
CL = 0x01;
CH = 0xA0;
tmpData = data;
for (int i = 0; i < tmpData.Length; i++)
{
CRC16Lo = (byte)(CRC16Lo ^ tmpData[i]); //每一个数据与CRC寄存器进行异或
for (Flag = 0; Flag <= 7; Flag++)
{
SaveHi = CRC16Hi;
SaveLo = CRC16Lo;
CRC16Hi = (byte)(CRC16Hi >> 1); //高位右移一位
CRC16Lo = (byte)(CRC16Lo >> 1); //低位右移一位
if ((SaveHi & 0x01) == 0x01) //如果高位字节最后一位为1
{
CRC16Lo = (byte)(CRC16Lo 0x80); //则低位字节右移后前面补1
} //否则自动补0
if ((SaveLo & 0x01) == 0x01) //如果LSB为1,则与多项式码进行异或
{
CRC16Hi = (byte)(CRC16Hi ^ CH);
CRC16Lo = (byte)(CRC16Lo ^ CL);
}
}
}
byte[] ReturnData = new byte[2];
ReturnData[0] = CRC16Hi; //CRC高位
ReturnData[1] = CRC16Lo; //CRC低位
return ReturnData;
}
}
-----在窗体中,建立经常使用的几个变量-----
用过微软SerialPort类的人,都遇到过这个尴尬,关闭串口的时候会让软件死锁。我们要了解一下SerialPort的实现和串口通讯机制,在你打开串口的时候,SerialPort会创建一个监听线程ListenThread,在这个线程中,等待注册的串口中断,当收到中断后,会调用DataReceived事件。调用完成后,继续进入循环等待,直到串口被关闭退出线程。这里我们就有了2个线程,UI主线程、串口监听线程。那么你在DataReceived处理数据的时候,就需要线程同步,避免并发冲突,什么是并发冲突?并发冲突就是2个或多个并行(至少看上去像)的线程运行的时候,多个线程共同的操作某一线程的资源,在时序上同时或没有按我们的预计顺序操作,这样就可能导致数据混乱无序或是彼此等待完成死锁软件。而串口程序大多是后者。详细解释可以参考CSDN的wuyazhe的博客。为此设置Listening和ClosingF两个标志位,避免并发冲突。
private SerialPort comm = new SerialPort();
//是否没有执行完invoke相关操作
private bool Listening = false;
//是否正在关闭串口,执行Application.DoEvents,并阻止再次invoke
private bool ClosingF = false;
// 用List替代数组有巨多优点。童叟无欺。
private List buffer = new List(4096);
------窗体载入事件-----
private void Form1_Load(object sender, EventArgs e)
{
//初始化SerialPort对象
comm.NewLine = "\r\n";
comm.RtsEnable = true;
//添加事件注册,每当串口收到数据,执行comm_DataReceived中的事件。
comm.DataReceived += comm_DataReceived;
}
--------发送指令写在timer1控件中,每0.5秒发送一次-------
private void timer1_Tick(object sender, EventArgs e)
{
//向PLC发送查询信息。从站02,功能码03,寄存器地址00,读取02个字节。
//校验码为 C4 38。
byte[] send_data=new byte[8]{0x02,0x03,0x00,0x00,0x00,0x02,0xC4,0x38};
comm.Write(send_data, 0, 8);
}
-----------comm_DataReceived事件-------------
void comm_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
//如果正在关闭,忽略操作,直接返回
//这样避免并发冲突,以免锁死。
if (ClosingF) return;
try
{
Listening = true;//设置标记,说明已经开始处理数据,要使用系统UI的。
int n = comm.BytesToRead;//先记录下来
byte[] buf = new byte[n];//声明一个数组存储当前的串口数据
comm.Read(buf, 0, n);//读取缓冲数据
buffer.AddRange(buf);//将缓冲数据添加到buffer。
//如果刚好为9个字节,则为正确的接受数据。如果不是,直接移除。
if (buffer.Count==9)
{
byte[] binary_data_1 = new byte[9];
byte[] binary_crc=new byte [7];
buffer.CopyTo(0, binary_data_1, 0, 9);
buffer.CopyTo(0, binary_crc, 0, 7);
buffer.Clear();
//实例化一个新的modbus类,使用其中的CRC校验函数。
modbus mb = new modbus();
//如果校验通过,则处理数据。注意分清高低位!
if (mb.CRC16(binary_crc)[0] == binary_data_1[8] && mb.CRC16(binary_crc)[1] == binary_data_1[7])
{
//将4个字节的数据合并
string data = binary_data_1[3].ToString("X2") + binary_data_1[4].ToString("X2") + binary_data_1[5].ToString("X2") + binary_data_1[6].ToString("X2");
//将16进制转换为10进制
int hex = Convert.ToInt32(data, 16);
//显示十进制数据
this.Invoke((EventHandler)(delegate { this.textBox1.Text = hex.ToString(); }));
}
}
}
else
{
buffer.Clear();
}
finally
{
Listening = false;//使用完毕,ui可以关闭串口了!
}
}
---------按钮代码----------
private void buttonOpenClose_Click(object sender, EventArgs e)
{
//根据当前串口对象,来判断操作
if (comm.IsOpen)
{
//关闭标志位为true,
ClosingF = true;
while (Listening) Application.DoEvents();
//打开时点击,则关闭串口
timer1.Enabled = false;
comm.Close();
ClosingF = false;
}
else
{
//关闭时点击,则设置好端口,波特率后打开
comm.PortName = "COM1";
comm.BaudRate = 9600;
try
{
comm.Open();
}
catch (Exception ex)
{
//捕获到异常信息,创建一个新的comm对象,之前的不能用了。
comm = new SerialPort();
//现实异常信息给客户。
MessageBox.Show(ex.Message);
}
//使能timer1,开始向S7-200发送数据。
timer1.Enabled = true;
}
//设置按钮的Text
buttonOpenClose.Text = comm.IsOpen ? "关闭" : "打开";
}


程序有许多不完善之处,比如 数据接受的如果不是9个字节,直接删除此数据了。
程序不够灵活,只使用于读寄存器的数。只要相应修改,便可实现其他功能。读写线圈等等。
第一次技术贴。关于s7-200Modbus从站的上位机软件设计。 已锁定
编辑推荐: 关闭

请填写推广理由:

本版热门话题

SIMATIC S7-200

共有33290条技术帖

相关推荐

热门标签

相关帖子推荐

guzhang

恭喜,你发布的帖子

评为精华帖!

快扫描右侧二维码晒一晒吧!

再发帖或跟帖交流2条,就能晋升VIP啦!开启更多专属权限!

  • 分享

  • 只看
    楼主

top
X 图片
您收到0封站内信:
×
×
信息提示
很抱歉!您所访问的页面不存在,或网址发生了变化,请稍后再试。