1. 串口硬件信号定义
串口通信(Serial Communications)是指外设和计算机间通过数据信号线、地线等按位(bit)进行传输数据的一种通信方式,属于串行通信方式,能够实现远距离通信,长度可达1200米。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通信的端口,这些参数必须匹配。
串口通信的接口标准有很多,有 RS-232C、RS-232、RS-422A、RS-485 等。常用的就是 RS-232 和 RS-485。串口通信使用的大多都是 DB9 接口,如下图。
1 载波检测(DCD)
2 接受数据(RXD)
3 发出数据(TXD)
4 数据终端准备好(DTR)
5 信号地线(SG)
6 数据准备好(DSR)
7 请求发送(RTS)
8 清除发送(CTS)
9 振铃指示(RI)
DB9 Connector 信号定义。串口测试将2、3针脚短接即可。
这里我们以 RS-232 接口进行演示。
1、数据包格式定为(10bytes):
帧头(0xAA,0x55),命令字(1byte),地址位(2bytes),数据位(2bytes),校验位(1byte,和校验),帧尾(0xFE,0xFE)
地址位和数据位都是高位在前。
2、串口端口号搜索
string[] portList = System.IO.Ports.SerialPort.GetPortNames();
for (int i = 0; i < portList.Length; i++)
{string name = portList[i];comboBox.Items.Add(name);
}
还有一种通过调用API的方法来获取实现,可以获取详细的完整串口名称,对于USB-to-COM虚拟串口来说特别适用。
3、串口属性参数设置
SerialPort mySerialPort = new SerialPort("COM2");//端口
mySerialPort.BaudRate = 9600;//波特率
mySerialPort.Parity = Parity.None;//校验位
mySerialPort.StopBits = StopBits.One;//停止位
mySerialPort.DataBits = 8;//数据位
mySerialPort.Handshake = Handshake.Non;
mySerialPort.ReadTimeout = 1500;
mySerialPort.DtrEnable = true;//启用数据终端就绪信息
mySerialPort.Encoding = Encoding.UTF8;
mySerialPort.ReceivedBytesThreshold = 1;//DataReceived触发前内部输入缓冲器的字节数
mySerialPort.DataReceived += new SerialDataReceivedEvenHandler(DataReceive_Method);mySerialPort.Open();
4、串口发送信息
- Write(Byte[], Int32, Int32) :将指定数量的字节写入串行端口
- Write(Char[], Int32, Int32) :将指定数量的字符写入串行端口
- Write(String) :将指定的字符串写入串行端口
- WriteLine(String) :将指定的字符串和NewLine值写入输出缓冲区
// Write a string
port.Write("Hello World");// Write a set of bytes
port.Write(new byte[] { 0x0A, 0xE2, 0xFF }, 0, 3);// Close the port
port.Close();
5. 串口接收信息
- Read(Byte[], Int32, Int32):从SerialPort输入缓冲区读取一些字节,并将那些字节写入字节数组中指定的偏移量处
- ReadByte():从SerialPort输入缓冲区中同步读取一个字节
- ReadChar(): 从SerialPort输入缓冲区中同步读取一个字符
- ReadExisting() :在编码的基础上,读取SerialPort对象的流和输入缓冲区中所有立即可用的字节
- ReadLine() :一直读取到输入缓冲区中的NewLine值
- ReadTo(String) :一直读取到输入缓冲区中的指定value的字符串
string serialReadString;
private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{serialReadString = port.ReadExisting());this.txt1.Invoke( new MethodInvoker(delegate { this.txt1.AppendText(serialReadString); }));
}
6、循环接收数据
void com_DataReceived(object sender, SerialDataReceivedEventArgs e)
{// Use either the binary OR the string technique (but not both)// Buffer and process binary datawhile (com.BytesToRead > 0)bBuffer.Add((byte)com.ReadByte());ProcessBuffer(bBuffer);// Buffer string datasBuffer += com.ReadExisting();ProcessBuffer(sBuffer);
}private void ProcessBuffer(string sBuffer)
{// Look in the string for useful information// then remove the useful data from the buffer
}private void ProcessBuffer(List<byte> bBuffer)
{// Look in the byte array for useful information// then remove the useful data from the buffer
}
7、 C# 串口接收数据不完整解决办法
/针对数据协议:head + len + playload + check 类型private List<byte> buffer = new List<byte>(4096);private void sp_DataReceived(objectsender, EventArgs e) //sp是串口控件
{int n = sp.BytesToRead;byte[] buf = new byte[n];sp.Read(buf, 0, n);//1.缓存数据buffer.AddRange(buf);//2.完整性判断while (buffer.Count >= 4) //至少包含帧头(2字节)、长度(1字节)、校验位(1字节);根据设计不同而不同{//2.1 查找数据头if (buffer[0] == 0x01) //传输数据有帧头,用于判断{int len = buffer[2];if (buffer.Count < len + 4) //数据区尚未接收完整{break;}//得到完整的数据,复制到ReceiveBytes中进行校验byte[] ReceiveBytes = new byte[len + 4];buffer.CopyTo(0, ReceiveBytes, 0, len + 4);byte jiaoyan; //开始校验---自定义实现jiaoyan = this.JY(ReceiveBytes);//if (jiaoyan != ReceiveBytes[len+3]) //校验失败,最后一个字节是校验位{buffer.RemoveRange(0, len + 4);MessageBox.Show("数据包不正确!");continue;}buffer.RemoveRange(0, len + 4);///执行对数据进行处理操作RunReceiveDataCallback(ReceiveBytes);}else //帧头不正确时,记得清除{buffer.RemoveAt(0);}}
}
//针对协议类型: head + len +cmd + seq+ playload +check + tail; private List<byte> buffer = new List<byte>(4096);
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e){ try{try{int nCount = serialPort1.BytesToRead;if (nCount == 0){return;}byte[] btAryBuffer = new byte[nCount];serialPort1.Read(btAryBuffer, 0, nCount);//缓存数据buffer.AddRange(btAryBuffer);int index = 1;while (buffer.Count>0x07) //最短协议长度{if (buffer[0] == 0x01) //协议头{if (buffer[index] != 0x03) //查询协议尾{index++;if (index > buffer.Count) //没有接受到帧尾 0x03{break; //退出继续接收}}else // 接收到协议尾 得到完整一帧数据{byte[] ReceiveBytes = new byte[index+1];buffer.CopyTo(0, ReceiveBytes, 0, index+1);RunReceiveDataCallback(ReceiveBytes);buffer.RemoveRange(0, index);}}else{buffer.RemoveAt(0);}} }catch (System.Exception ex){}}catch(Exception ex){MessageBox.Show(ex.Message);}finally{handerListening = false;}}
8、封装
1、数据封装方法:
//数据打包
private byte[] DataPackage(byte cmd, int addr, int data){byte[] package = new byte[10];package[0] = 0xAA;//帧头package[1] = 0x55;package[2] = cmd;//命令字 byte[] dataaddr = IntToByteArray(addr);package[3] = dataaddr[0];//地址高字节package[4] = dataaddr[1];//地址低字节byte[] value = IntToByteArray(data);package[5] = value[0];//数据高字节package[6] = value[1];//数据低字节package[7] = CheckSum(package);//校验位package[8] = 0xFE;//帧尾package[9] = 0xFE;return package;}//将int转换成2位数组private static byte[] IntToByteArray(int value){int hvalue = (value >> 8) & 0xFF;int lValue = value & 0xFF;byte[] arr = new byte[] { (byte)hvalue, (byte)lValue };return arr;}//得到和校验码private byte CheckSum(byte[] package){byte checksum = 0;for (int i = 0; i < package.Length; i++){checksum += package[i];}return checksum;}
2、串口调用封装类CommHelper.cs
internal class CommHelper{//委托public delegate void EventHandle(byte[] readBuffer);public event EventHandle DataReceived;public SerialPort serialPort;private Thread thread;volatile bool _keepReading;public CommHelper(){serialPort = new SerialPort();thread = null;_keepReading = false;serialPort.ReadTimeout = -1;serialPort.WriteTimeout = 1000;}//获取串口打开状态public bool IsOpen{get{return serialPort.IsOpen;}}//开始读取private void StartReading(){if (!_keepReading){_keepReading = true;thread = new Thread(new ThreadStart(ReadPort));thread.Start();}}//停止读取private void StopReading(){if (_keepReading){_keepReading = false;thread.Join();thread = null;}}//读数据private void ReadPort(){while (_keepReading){if (serialPort.IsOpen){int count = serialPort.BytesToRead;if (count > 0){byte[] readBuffer = new byte[count];try{Application.DoEvents();serialPort.Read(readBuffer, 0, count);DataReceived?.Invoke(readBuffer);Thread.Sleep(100);}catch (TimeoutException){}}}}}//写数据public void WritePort(byte[] send, int offSet, int count){if (IsOpen){serialPort.Write(send, offSet, count);}}//打开public void Open(){Close();try{serialPort.Open();serialPort.RtsEnable = true;if (serialPort.IsOpen){StartReading();}else{MessageBox.Show("串口打开失败!");}}catch{}}//关闭public void Close(){StopReading();serialPort.Close(); }}