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 data
while (com.BytesToRead > 0)
bBuffer.Add((byte)com.ReadByte());
ProcessBuffer(bBuffer);
// Buffer string data
sBuffer += 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();
}
}