工作中寫的三菱PLC串口通訊,封裝成了一個類,可以方便隨時調用;
數據傳送分為 循環 和 一次性 兩種方式;
為了避免沖突,數據的收發使用了一個線程來排隊完成。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; using System.Threading; using System.IO.Ports; namespace 三菱PLC讀寫 { class mitsubishi { Thread mitsubishiTread; //mitsubishi SerialPort m_sp; int m_sendAddr, m_sendLen, m_recieveAddr, m_recieveLen; bool[] m_Q = new bool[5]; //是否已請求 bool[] m_R = new bool[5]; //是否已回復 char[] m_T = new char[5]; //類型 int[] m_A = new int[5]; //地址 char[] m_C = new char[5]; //命令 int[] m_D = new int[5]; //發出數據 bool[] m_R_Bool = new bool[5]; //返回值 int[] m_R_Int = new int[5]; //返回值 public byte[] m_wData, m_rData; //循環寫和讀數據 char[] m_WData; //循環寫數據轉換格式 bool circle = false; //是否循環讀寫,判斷不同字節長度 #region mitsubishi類構造函數 public mitsubishi(string portName, int sendA, int sendL, int revA, int revL) //(串口號、寫入起始地址、寫入字節長度、讀取起始地址、讀取字節長度) { m_sp = new SerialPort(); m_sp.PortName = portName; m_sp.BaudRate = 9600; m_sp.DataBits = 7; m_sp.StopBits = (StopBits)1; m_sp.Parity = Parity.Even; m_sp.ReadTimeout = 500; //收發數據起始地址和字節長度 m_sendAddr = sendA; m_sendLen = sendL; m_recieveAddr = revA; m_recieveLen = revL; m_wData = new byte[sendL]; m_rData = new byte[revL]; m_WData = new char[sendL*2]; if (!m_sp.IsOpen) { m_sp.Open(); Thread.Sleep(500); //串口打開,等待0.5S后開始啟動線程 mitsubishiTread = new Thread(new ThreadStart(start)); mitsubishiTread.Start(); } else { MessageBox.Show("串口已被打開,請檢查端口!"); m_sp.Close(); } } #endregion #region 單線程排隊發送 public void start() { while (true) { //這里用排序發送 if (!m_sp.IsOpen) { m_sp.Open(); Thread.Sleep(500); //串口打開,等待0.5S后開始發送 } //有排隊先完成 m_First(); //循環寫部分 circle = true; for (int i = 0; i < m_sendLen; i++) { m_WData[i * 2] = Tran2(m_wData[i] / 0x10); m_WData[i * 2 + 1] = Tran2(m_wData[i] % 0x10); } char[] m_write = SendData('d', m_sendAddr, 'w', m_WData); if(m_write[0] - 6 != 0) { MessageBox.Show("循環寫入數據出錯!"); } //有排隊先完成 m_First(); //循環讀部分 circle = true; char[] m_read = SendData('d', m_recieveAddr, 'r', m_WData); if(m_read.Length/2 == m_recieveLen) { for (int i = 0; i < m_read.Length; i = i + 2) { m_rData[i/2] = (byte)(Tran1(m_read[i])*0x10 + Tran1(m_read[i+1])); } } else { MessageBox.Show("循環讀出數據出錯!"); } } } #endregion #region 優先處理 private void m_First() { circle = false; for (int i = 0; i < 5; i++) { if (m_Q[i] && !m_R[i]) //有請求但無回復 { int m_Data_Len; if (m_C[i] == 'D' || m_C[i] == 'S') { m_Data_Len = 8; } else { m_Data_Len = 4; } char[] m_Data = HTC(m_D[i], m_Data_Len); char[] m_return = SendData(m_T[i], m_A[i], m_C[i], m_Data); int n = m_return.Length; if (n > 1) { m_R_Int[i] = 0; for (int j = n - 1; j > 0; j = j - 2) { m_R_Int[i] *= 0x100; m_R_Int[i] += (Tran1(m_return[j - 1]) * 0x10 + Tran1(m_return[j])); } } else if (n == 1 && m_return[0] - 6 == 0) { m_R_Bool[i] = true; } m_R[i] = true; } } } #endregion #region 編碼轉換 private int Tran1(Char InChar) //ASCII碼轉數字,接收時使用 { int OutInt; if (InChar < 0x40) { OutInt = InChar - 0x30; } else { OutInt = InChar - 0x37; } return OutInt; } private Char Tran2(int InChar) //數字轉ASCII碼,發送時使用 { Char OutChar; if (InChar < 10) { OutChar = (Char)(InChar + 0x30); } else { OutChar = (Char)(InChar + 0x37); } return OutChar; } #endregion #region 發送或接收數據,返回Char數組 private Char[] SendData(Char type, int startAdd, Char cmd, char[] inChar) //(S、D、M、X、Y、T、C) (E00/1000/100/80/A0/C0/1C0) (0、1、w/W、r/R) { Char[] outChar = null; //報文長度 int n = 0; if (cmd == '0' || cmd == '1') // 0、1復位置位 { n = 9; } else if (cmd == 'R' || cmd == 'r') // R/r讀單雙字 { n = 11; } else if (cmd == 'w' || cmd == 'W') // w寫單字 { n = 11 + inChar.Length; } char[] subuffer = new char[n]; //報文首 subuffer[0] = (char)2; //命令代碼 if (cmd == '0') { subuffer[1] = (char)0x38; } else if (cmd == '1') { subuffer[1] = (char)0x37; } else if (n == 11) { subuffer[1] = (char)0x30; } else { subuffer[1] = (char)0x31; } //首地址 int Addr = 0; if (type == 'S' || type == 's') // S、s { Addr = 0xE00; } else if (type == 'D' || type == 'd') // D、d { Addr = 0x1000; } else if (type == 'M' || type == 'm') // M、m { Addr = 0x100; } else if (type == 'X' || type == 'x') // X、x { Addr = 0x80; } else if (type == 'Y' || type == 'y') // Y、y { Addr = 0xA0; } else if (type == 'T' || type == 't') // T、t { Addr = 0xC0; } else if (type == 'C' || type == 'c') // C、c { Addr = 0x1C0; } //計算首地址 Char[] Addrs = new Char[4]; if (n == 9) { Addr = Addr * 8 + startAdd; Addrs = HTC(Addr, 4); } else { Addr += (startAdd * 2); Addrs = StartData(Addr); } Addrs.CopyTo(subuffer, 2); //字節長度 if (circle) { if (cmd == 'R' || cmd == 'W') // R/W 讀寫雙字 { subuffer[7] = Tran2(m_sendLen % 0x10); subuffer[6] = Tran2 (m_sendLen / 0x10); } else if (cmd == 'r' || cmd == 'w') // r/w 讀寫單字 { subuffer[7] = Tran2(m_recieveLen % 0x10); subuffer[6] = Tran2 (m_recieveLen / 0x10); } } else { subuffer[6] = (char)0x30; if (cmd == 'R' || cmd == 'W') // R/W 讀寫雙字 { subuffer[7] = (char)0x34; } else if (cmd == 'r' || cmd == 'w') // r/w 讀寫單字 { subuffer[7] = (char)0x32; } } //數據內容 if (cmd == 'w' || cmd == 'W') { inChar.CopyTo(subuffer, 8); } //報文尾 subuffer[n - 3] = (char)3; //校驗碼 Char[] T = new Char[2]; T = CCD(subuffer, n - 3); T.CopyTo(subuffer, n - 2); if (m_sp.IsOpen) { m_sp.Write(subuffer, 0, n); //MessageBox.Show("已發送成功!"); for (int i = 0; i < 5; i++) { Thread.Sleep(100); int Len = m_sp.BytesToRead; if (Len > 0) { i = 5; //跳出循環 char[] RecieveBuf = new char[Len]; m_sp.Read(RecieveBuf, 0, Len); if (Len == 1) //非寫入或出錯狀態 { outChar = RecieveBuf; } else //寫入狀態,讀取返回數據 { T = CCD(RecieveBuf, Len - 3); //驗證接收信息校驗碼 if (T[0] == RecieveBuf[Len - 2] && T[1] == RecieveBuf[Len - 1]) { Char[] outChar2 = new Char[Len - 4]; Array.Copy(RecieveBuf, 1,outChar2, 0, Len - 4); outChar = outChar2; } } } } } return outChar; } #endregion #region 和校驗 private char[] CCD(char[] InChar, int len) //和校驗 { int S = 3; for (int i = 1; i < len; i++) { S += InChar[i]; } char[] OutChar = HTC(S % 0x100, 2); return OutChar; } #endregion #region 字元件起始地址轉化 private char[] StartData(int inData) //字元件起始地址轉化 { int t = inData; int[] tt = new int[4]; char[] H = new char[4]; for (int i = 3; i >= 0; i--) { tt[i] = t % 0x10; t = t / 0x10; H[i] = Tran2(tt[i]); } return H; } #endregion #region 位元件起始地址、發送數據轉化 public char[] HTC(int inData, int inBit) //位元件起始地址、發送數據轉化 { byte[] TranByte = System.BitConverter.GetBytes(inData); int len = TranByte.Length; if (2 * len > inBit) { len = inBit / 2; } char[] OutChar = new char[inBit]; for (int i = 0; i < len; i++) { int HL = TranByte[i]; int H = HL / 0x10; OutChar[2 * i] = Tran2(H); int L = HL % 0x10; OutChar[2 * i + 1] = Tran2(L); } return OutChar; } #endregion #region 讀SD/D public int read(string s) { int outInt = 0; if (System.Text.RegularExpressions.Regex.IsMatch(s, @"^[dDsS]\d{1,4}$+")) { Char[] inChar = s.ToCharArray(0, 1); Char type = inChar[0],cmd; if (type == 'd' || type == 's') { cmd = 'r'; } else { cmd = 'R'; } int addr = Convert.ToInt32(s.Substring(1)); int n = 5; for (int i = 0; i < 5; i++) { if (!m_Q[i]) { n = i; i = 5; m_Q[n] = true; m_T[n] = type; m_A[n] = addr; m_C[n] = cmd; m_R[n] = false; m_R_Bool[n] = false; } } for (int i = 0; i <= 5; i++) { Thread.Sleep(100); if (m_R[n]) { m_Q[n] = false; m_R[n] = false; outInt = m_R_Int[n]; i = 5; } } } else { MessageBox.Show("指令格式錯誤!"); } return outInt; } #endregion #region 寫SD/D public bool write(string s,int d) { bool outBool = false; if (System.Text.RegularExpressions.Regex.IsMatch(s, @"^[dDsS]\d{1,4}$+")) { Char type = s.ToCharArray(0, 1)[0], cmd; if (type == 'd' || type == 's') { cmd = 'w'; } else { cmd = 'W'; } int addr = Convert.ToInt32(s.Substring(1)); int n = 5; for (int i = 0; i < 5; i++) { if (!m_Q[i]) { n = i; i = 5; m_Q[n] = true; m_T[n] = type; m_A[n] = addr; m_C[n] = cmd; m_D[n] = d; m_R[n] = false; m_R_Bool[n] = false; } } for (int i = 0; i <= 5; i++) { Thread.Sleep(100); if (m_R[n]) { m_Q[n] = false; m_R[n] = false; outBool = m_R_Bool[n]; i = 5; } } } else { MessageBox.Show("指令格式錯誤!"); } return outBool; } #endregion #region 置位X/Y/M/T/C public bool set(string s) { bool outBool = false; if (System.Text.RegularExpressions.Regex.IsMatch(s, @"^[xXyYmMtTcC]\d{1,4}$+")) { Char[] inChar = s.ToCharArray(0, 1); Char type = inChar[0]; int addr = Convert.ToInt32(s.Substring(1)); int n = 5; for (int i = 0; i < 5; i++) { if (!m_Q[i]) { n = i; i = 5; m_Q[n] = true; m_T[n] = type; m_A[n] = addr; m_C[n] = '1'; m_R[n] = false; m_R_Bool[n] = false; } } for (int i = 0; i <= 5; i++) { Thread.Sleep(100); if (m_R[n]) { m_Q[n] = false; m_R[n] = false; outBool = m_R_Bool[n]; i = 5; } } } else { MessageBox.Show("指令格式錯誤!"); } return outBool; } #endregion #region 復位X/Y/M/T/C public bool rst(string s) { bool outBool = false; if (System.Text.RegularExpressions.Regex.IsMatch(s, @"^[xXyYmMtTcC]\d{1,4}$+")) { Char[] inChar = s.ToCharArray(0, 1); Char type = inChar[0]; int addr = Convert.ToInt32(s.Substring(1)); int n = 5; for (int i = 0; i < 5; i++) { if (!m_Q[i]) { n = i; i = 5; m_Q[n] = true; m_T[n] = type; m_A[n] = addr; m_C[n] = '0'; m_R[n] = false; m_R_Bool[n] = false; } } for (int i = 0; i <= 5; i++) { Thread.Sleep(100); if (m_R[n]) { m_Q[n] = false; m_R[n] = false; outBool = m_R_Bool[n]; i = 5; } } } else { MessageBox.Show("指令格式錯誤!"); } return outBool; } #endregion } }
在程序中調用封裝的類:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Threading; using System.IO.Ports; namespace 三菱PLC讀寫 { public partial class Form1 : Form { mitsubishi mit = new mitsubishi("COM6",0,10,10,10); public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { } //讀 private void button1_Click(object sender, EventArgs e) { int result1 = mit.read("D100"); //讀D100雙字 int result2 = mit.read("d100"); //讀D100單字 } //將100寫入到D8340雙字 private void button2_Click_1(object sender, EventArgs e) { bool result = mit.write("S340", 100); } //置位 private void button3_Click_1(object sender, EventArgs e) { bool result = mit.set("M0"); } //復位 private void button4_Click(object sender, EventArgs e) { bool result = mit.rst("Y0"); } //循環寫入D1,D1位置在2、3兩個字節 private void button5_Click(object sender, EventArgs e) { int sendData = 5678; mit.m_wData[2] = (byte)(sendData % 0x100); mit.m_wData[3] = (byte)(sendData / 0x100); } //循環讀取D12,D12位置在4、5兩個字節 private void button6_Click(object sender, EventArgs e) { int revData = mit.m_rData[4] + mit.m_rData[5] * 0x100; } } }