本人最近做了一个上位机与欧姆龙PLC通信的系统。与大家分享分享心得吧!废话不多说,先看看实际的效果图吧!由于本人能力有限,请大家多多指正吧!要想做好上位机,就必须搞好通信问题。欧姆龙PLC与上位机通信采用的是HostLink协议。下面就先来介绍介绍HostLink协议。使用HostLink协议要注意以下几点:(1)通信线的连线,RS232口接线。一般使用无握手信号连线,无握手信号连线仅需要3根线,1根地线,2根信号线:发送线TXD,接受线RXD。(2)通信参数的设置,一般我们VisualStdio中选用控件SerialPort,在其属性中选择波特率9600,数据位7位,偶检验,2个停止位。在调试的时候可以用串口助手先进行调试,调试明白了再开始写上位机也不迟。(3)HostLink协议的格式,一般包括起始符,单元号,正文,FCS检验码,结束符。起始符都是@,一般你的上位机控制的只有一个下位机,那么你的单元号就是00,正文内容是举一个例子,比如说@00WD0100123456*CR,@是起始符,00是单元号,WD0100表示向DM区0100,写入数据,写入的就是1234(写的是字符,传入PLC中自动变成16进制的1234),56是FCS校验,FCS校验就是校验码前面所有的字符按其ASCII码异或,结果是两位的。在写上位机之前你也可以现写一个FCS校验器,反正后续的代码总是要写的。*CR是结束符,*比较容易打出来,CR就是回车键,程序中表现时可以用”\r”来表示。再比如说@00RD0100000156*CR是向DM0100开始的数据区,连续读0001个数据,在这里也就是连续读一个数据。当然你给PLC发一个数据,如果格式正确,PLC也会响应你一个数据,见下表。在这里讲一下返回的状态码,根据状态码,我们可以知道发送的数据有没有出现错误。正常情况下,返回的只能是00,如果不是00,请根据上表,自己修改错误。下面我先把我写的FCS校验码贴出来吧,代码如下:usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel;usingSystem.Data;usingSystem.Drawing;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;usingSystem.Windows.Forms;namespace异或测试{publicpartialclassForm1:Form{publicForm1(){InitializeComponent();}privatevoidbutton1_Click(objectsender,EventArgse){stringstr=textBox1.Text;Int16[]a=newInt16[str.Length];//将字符串中的一个个字符,分离出来,并保存for(inti=0;istr.Length;i++){a[i]=Convert.ToInt16(str[i]);}Int16FCS=0;for(inti=0;istr.Length;i++){FCS=(Int16)(FCS^a[i]);//异或计算FCS码}stringstr_FCS;//把计算出来的FCS码再转成字符串str_FCS=FCS.ToString(x2);textBox2.Text=str_FCS;}}}写的不太好,就当自己测试的时候用了。特别要注意的是,PLC在运行状态是不能向DM区写数据的。必要的时候我们可以通过向PLC发送不同指令来改变其工作模式。指令如下:模式的改变PROGRAM@00SC0050*MONITOR@00SC0252*RUN@00SC0353*00是站號最基本的东西已经说得比较清楚了,下面把代码也贴出来吧,同样代码也写的比较乱,让内行人见笑了。大体上看一共就用了这几个事件。下面是其具体的代码:本人做的是一个恒压供水的项目,这里只是一个产品的测试,故有很多地方还不严谨,思路不是很清楚,我感觉这也我一周弄出来的,还是比较不错的usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel;usingSystem.Data;usingSystem.Drawing;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;usingSystem.Windows.Forms;usingSystem.IO.Ports;namespaceWindowsFormsApplication2{publicpartialclassForm1:Form{publicForm1(){InitializeComponent();System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls=false;//关闭线程检查}privatevoidForm1_Load(objectsender,EventArgse){//窗口载入时,自动搜索可用的串口stringbuffer;comboBox1.Items.Clear();for(inti=0;i20;i++){try{buffer=COM+i.ToString();serialPort1.PortName=buffer;serialPort1.Open();comboBox1.Items.Add(buffer);serialPort1.Close();}catch{}}serialPort1.WriteLine(MONITOR@00SC0252*+\r);//让PLC工作在监视模式下//serialPort1.WriteLine(@00WD0201000050*+\r);}privatevoidserialPort1_DataReceived(objectsender,SerialDataReceivedEventArgse){//串口接受事件所触发执行if(radioButton1.Checked){//如果处于运行状态,则接受的字符串只显示在接受数据去//高级功能中的接受面板不予显示stringstr=serialPort1.ReadExisting();//接受到的字符stringstr1;//通道1的字符//stringstr2;//通道2的字符//该设备没有外接stringstr3;//通道3的字符//stringstr4;//通道4的字符//该设备没有外接stringstr_Z_S_PLC;//转速的字符PLC模块所采集的try{str1=str.Substring(8,2);}catch{str1=;}S_Y_1.Text=str1;Application.DoEvents();//System.Threading.Thread.Sleep(100);try{str3=str.Substring(12,2);}catch{str3=;}S_Y_3.Text=str3;Application.DoEvents();/******************///System.Threading.Thread.Sleep(100);//让线程在此休眠100MS,以便处理//其他线程的事情。//这对刷屏,text属性用比较大的作用S_Y_2.Text=60;S_Y_4.Text=60;Application.DoEvents();/******************/try{str_Z_S_PLC=str.Substring(15,3);}catch{str_Z_S_PLC=;//str_Z_S_PLC=str.Substring(15,3);}S_B_Z_S.Text=str_Z_S_PLC;Application.DoEvents();/******************/}else{stringstr_gaoji=serialPort1.ReadExisting();//接受到的字符textBox1.AppendText(str_gaoji);}}privatevoidbutton2_Click(objectsender,EventArgse){//高级功能中的发送按钮所执行的功能if(serialPort1.IsOpen){try{//若串口为打开状态则,发送命令serialPort1.WriteLine(textBox2.Text);}catch{MessageBox.Show(数据写入有错,错误);}}}privatevoidbutton1_Click(objectsender,EventArgse){//打开串口按钮执行的功能if(serialPort1.IsOpen){try{//若串口原状态是开,再按一次则关闭穿口serialPort1.Close();button1.Text=打开串口;ovalShape1.FillColor=Color.Gray;}catch{MessageBox.Show(串口关闭错误,错误);}}else{try{//若串口状态原先是关闭,则按一次打开串口serialPort1.PortName=comboBox1.Text;serialPort1.Open();button1.Text=关闭串口;ovalShape1.FillColor=Color.GreenYellow;}catch{MessageBox.Show(串口打开错误,错误);}}}privatevoidtimer1_Tick(objectsender,EventArgse){//每隔1S,上位机给PLC发送一条指令//PLC返回一条指令,用于刷新,各采集的数据if(serialPort1.IsOpen&radioButton1.Checked){serialPort1.WriteLine(@00RD0100000354*+\r);}else{}}privatevoidtrackBar1_Scroll(objectsender,EventArgse){trackBar1.Minimum=0;trackBar1.Maximum=0xff;if(radioButton2.Checked){//首先要改变PLC的工作模式serialPort1.WriteLine(PROGRAM@00SC0050*+\r);Int16value;//value用于保存滑动条的当前值value=(Int16)trackBar1.Value;//获取value值//系统状态监测if(value0xee){label10.BackColor=Color.Red;label8.BackColor=Color.Lime;label9.BackColor=Color.Lime;}elseif(value0x10){label10.BackColor=Color.Lime;label8.BackColor=Color.Lime;label9.BackColor=Color.Red;}else{label10.BackColor=Color.Lime;label8.BackColor=Color.Lime;label9.BackColor=Color.Lime;}S_B_Z_S.Text=value.ToString();//将滑动条的整型值转换成字符串stringvalue1=value.ToString(x4).ToUpper();//FCS校验intFCS=0;stringContant;//指令前面固定的字符Contant=@00WD0102;Int16[]a=newInt16[Contant.Length];//将字符串中的一个个字符,分离出来,并保存Int16[]a1=newInt16[value1.Length];for(int