串口通信
using system.IO.Ports;
SerialPort类
1.获取机器的所有串口
/*获得设备的所有串口*/ //类SerialPort的静态方法GetPortNames()返回一台设备所有的串口名字 //如果机器无串口,返回一个空的字符串数组的引用【不是null】 string[] DevideAllports = SerialPort.GetPortNames(); if(DevideAllports.Length == 0) { Console.WriteLine("本机无串口"); } else { foreach (var item in DevideAllports) { Console.WriteLine(item); } }
2.操纵设备的一个串口
两台设备之间实现串口通信,二者使用的串口的(1)数据位 (2)停止位 (3)奇偶校验方式 (4)波特率 必须相同。
//1.创建一个实例 SerialPort DeciveSp = new SerialPort(); //2.绑定串口 DeciveSp.PortName = "COM1"; //3.设置通信参数 DeciveSp.DataBits = 8;//设置数据位 DeciveSp.StopBits = StopBits.One;//设置停止位 DeciveSp.BaudRate = 9600;//设置波特率 DeciveSp.Parity = Parity.Even;//设置奇偶校验方式
3.打开和关闭一个串口
/*打开串口*/ if (!DeciveSp.IsOpen) { DeciveSp.Open(); } /*关闭串口*/ if (DeciveSp.IsOpen) { DeciveSp.Close(); }
4.发送数据
public void Write (string str); public void Write (byte[] buffer, int offset, int count); public void Write (char[] buffer, int offset, int count); public void WriteLine (string str);
5.接收数据
public delegate void SerialDataReceivedEventHandler(object sender, SerialDataReceivedEventArgs e); public event SerialDataReceivedEventHandler DataReceived;
C#中SerialPort类中DataReceived事件,当机器收到数据时会激发中断处理程序,在辅助线程上引发 DataReceived 事件;
device.ReceivedBytesThreshold = 1;表示机器串口硬件缓存收到一个字节,就触发DataReceived事件,但是C#并不能保证接收到的每一个字节都会触发一次DataReceived事件。
所以:
(1)触发一次,可以使用BytesToRead循环获取缓冲区内接收到的所有数据。
(2)由于Read()是在辅助线程上执行的,不是在主线程上执行的,所以在DataRecived事件绑定的方法中,必须使用Control.Invoke()方法修改UI;
应用场景一:通信双方发送的消息不长(低于1024 bits,约512个字符),各个命令的执行必须是互斥的,一发一答的过程不能被其他命令中断。
class MySerialPort { private SerialPort device = new SerialPort(); private AutoResetEvent waitRsp = new AutoResetEvent(false); private object locker = new object(); //用于同步触发串口接收数据的函数 byte[] bufferReceived = null;//接收缓存 public MySerialPort() { device.ReadBufferSize = 1024;//设置接收缓存的大小,一般容纳下最长消息即可 device.DataReceived += new SerialDataReceivedEventHandler(device_ReceiveData); } private void device_ReceiveData(object sender,SerialDataReceivedEventArgs e) { if(device.BytesToRead > 0) { lock (locker) { if(device.BytesToRead > 0) { while (device.BytesToRead > 0) { bufferReceived = new byte[device.BytesToRead]; device.Read(bufferReceived, 0, device.BytesToRead); } waitRsp.Set(); //告诉处理命令的方法,已经收到回复信息,请继续往下执行 } } } } }
串口编程注意事项:
(1)DataReceived事件中,最高效的做法是指缓存数据,然后异步的去分析数据。但是,这样较复杂,在效率要求不是很高的情况下(大多数情况),可以在DataReceived事件中缓存数据后,立刻进行数据完整性检查,有效性检查,分析数据,以及更新界面。但这么做有一个隐患,底层串口操作的效率依赖于数据分析和界面更新,任何一个环节频繁耗时过长,都会造成数据的堆积。文章只假设都不拖时间的情况。
(2)一答一回的指令可以使用AutoResetEvent在发完数据后,在DataReceived事件中set()来同步
(3)相互独立的指令交互过程,可以使用Interlocked锁进行同步,这样在用户多个线程调用指令时,也不会发生错误。
(4)发送指令不能太快,否则可能造成设备处理不及,可以在发送前,判断一下(当前时间 - 上一次收到数据的时间)是否满足设备的响应时长。
(5)考虑重发问题。