客戶需要一個電子看板,類似一個電視大小的, 可以顯示生產的型號,單號數量等資訊. 電子看板是用Modbus TCP通訊的. 生產線每完成一件產品的測試,掃碼打包後, 實際產量要增加1, 所以要和生產資料庫連線起來
下載一個開源的C# Modbus的工具 https://github.com/stephan1827/modbusTCP-DotNET
裡面關鍵程式碼就是構建Modbus TCP的Header
報文頭 報文的序列號2位元組, 00 00表示ModbusTCP協議,資料長度2位元組,裝置地址1位元組,
功能碼為1位元組,暫存器地址2位元組,讀取長度2位元組
Modbus的操作物件有四種:線圈、離散輸入、保持暫存器、輸入暫存器。物件 Coil線圈 可讀可寫DiscreteInputs離散量 只讀InputRegister輸入暫存器 只讀HoldingRegiste保持暫存器 可讀可寫//功能碼 private const byte fctReadCoil = 1; private const byte fctReadDiscreteInputs = 2; private const byte fctReadHoldingRegister = 3; private const byte fctReadInputRegister = 4; private const byte fctWriteSingleCoil = 5; private const byte fctWriteSingleRegister = 6; private const byte fctWriteMultipleCoils = 15; private const byte fctWriteMultipleRegister = 16; private const byte fctReadWriteMultipleRegister = 23;
// ------------------------------------------------------------------------ // Create modbus header for read action private byte[] CreateReadHeader(ushort id, byte unit, ushort startAddress, ushort length, byte function) { byte[] data = new byte[12]; byte[] _id = BitConverter.GetBytes((short)id); data[0] = _id[1]; // Slave id high byte data[1] = _id[0]; // Slave id low byte data[5] = 6; // Message size ,資料長度 data[6] = unit; // Slave address 裝置地址 data[7] = function; // Function code 功能碼 byte[] _adr = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)startAddress)); data[8] = _adr[0]; // Start address 暫存器地址 data[9] = _adr[1]; // Start address byte[] _length = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)length)); data[10] = _length[0]; // Number of data to read 讀取長度 data[11] = _length[1]; // Number of data to read return data; } // ------------------------------------------------------------------------ // Create modbus header for write action private byte[] CreateWriteHeader(ushort id, byte unit, ushort startAddress, ushort numData, ushort numBytes, byte function) { byte[] data = new byte[numBytes + 11]; byte[] _id = BitConverter.GetBytes((short)id); data[0] = _id[1]; // Slave id high byte data[1] = _id[0]; // Slave id low byte byte[] _size = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)(5 + numBytes))); data[4] = _size[0]; // Complete message size in bytes data[5] = _size[1]; // Complete message size in bytes data[6] = unit; // Slave address data[7] = function; // Function code byte[] _adr = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)startAddress)); data[8] = _adr[0]; // Start address data[9] = _adr[1]; // Start address if (function >= fctWriteMultipleCoils) { byte[] _cnt = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)numData)); data[10] = _cnt[0]; // Number of bytes data[11] = _cnt[1]; // Number of bytes data[12] = (byte)(numBytes - 2); } return data; }
瞭解了ModBus TCP協議後,我們看看怎麼來讀寫暫存器的例子
try { // Create new modbus master and add event functions MBmaster = new Master("192.168.8.1", 502, true); MBmaster.OnResponseData += new ModbusTCP.Master.ResponseData(MBmaster_OnResponseData); MBmaster.OnException += new ModbusTCP.Master.ExceptionData(MBmaster_OnException); ////取得工人人數等資訊 ReadHoldingRegister(); lblMsg.Text = "連線成功"; } catch (SystemException error) { MessageBox.Show(error.Message); lblMsg.Text = error.Message; }
// ------------------------------------------------------------------------ // read holding register // 工人人數,計劃數量,實際產量,已耗時長,已耗時長顯示保留小數位數 存在地址1到6 // // ------------------------------------------------------------------------ private void ReadHoldingRegister() { ushort ID = 3; //自己定義的序號 byte unit = Convert.ToByte("1"); //Modus 地址1 ushort StartAddress = 0; //開始地址 UInt16 Length =6; //長度 if (MBmaster != null) { MBmaster.ReadHoldingRegister(ID, unit, StartAddress, Length); //非同步方法 } }
private void MBmaster_OnResponseData(ushort ID, byte unit, byte function, byte[] values) { // ------------------------------------------------------------------ // Seperate calling threads if (this.InvokeRequired) { this.BeginInvoke(new Master.ResponseData(MBmaster_OnResponseData), new object[] { ID, unit, function, values }); return; } // ------------------------------------------------------------------------ // Identify requested data switch (ID) { case 3: //"Read holding register"; //根據返回值陣列大小,來判斷哪個資料 int[] word = ConvertBytesToWords(values); if(word.Length==6) { Console.WriteLine("return ActualCnt=" + ActualCnt); WorkerCnt = word[0]; PlanCnt = word[1]; ActualCnt = word[2]; txtWorkerCnt.Text = WorkerCnt.ToString(); txtActual.Text = ActualCnt.ToString(); txtPlanCnt.Text = PlanCnt.ToString(); txtLeftCnt.Text = (PlanCnt - ActualCnt).ToString(); } break; case 8: //grpData.Text = "Write multiple register"; break; } }