串口通讯
- 前言
- 通讯协议
- 代码部分
- 引用文件的准备
- 数据类型的声明
- 写接收数据函数DataReceiveFunction()
- 写打开串口函数OpenPort()
- 关闭串口ClosePort()
- 发送数据WriteData(byte[] bys)
- 对于PLC端参考自由口协议向上方式即可,未来遇到什么古怪问题会持续更新。
前言
U3D和plc或者是单片机等其他软件的串口通讯脚本就是需要在上位机写一个串口服务程序,可以实现开启串口、关闭串口、接收、发送这四块基本内容即可,对于有数据处理的朋友可以再开一个线程专门用来数据处理。
这篇文章在稍微影响数据流的准确性下,将C#中Read()函数接收一次信息却分两次到多次读取的的问题解决了一点。
为了方便大家直接移植,以下代码均不带函数名称,将代码片直接复制至函数下即可。
2023年4月28日更新:
1.之前有发现当物理的硬件线路受到干扰时会出现断链或线程(协程)卡死的情况,之后发现极大可能是出现在外部干扰导致串行通讯出现乱码,该乱码导致接收线程中的序号错乱。本次主要更新接收线程中的接收模块校验问题。
2.同时如果出现读取不完全的情况很有可能是 软件读的速度>>串口接受数据的速度,解决方法是再接收事件函数的开始位置延迟一段时间,根据数据长度的不同可以适当改变延迟的时间,我这边延时了300ms
通讯协议
U3D为串口通讯协议,PLC为自由口协议。
代码部分
引用文件的准备
在U3D端需要使用
using System.IO.Ports;
这个引用中关于端口号、波特率、数据位、停止位、奇偶位五个参数的定义。
数据类型的声明
private string portName; //端口号
private int baudRate; //波特率
private Parity parity; //奇偶位
private int dataBits; //数据位
private StopBits stopBits;//停止位
五个参数定义好后,开始写四个基本的函数块,其中最有特点的即为接收数据函数,希望大家可以看看有没有更好的方法,我就在这抛砖引玉了。
写接收数据函数DataReceiveFunction()
int index = 0; //用于记录此时的数据次序
int ReadToBytes = 0;//记录长度
byte[] readBuffer = new byte[20];
int startIndex = -1;
while (true)
{
if (sp != null && sp.IsOpen)
{
try
{
Thread.Sleep(300);
int n = sp.BytesToRead;
if (n > 0)//此处是发现接收缓存区出现内容则进行读取
{
readBuffer = new byte[sp.BytesToRead + 1];
ReadToBytes = sp.Read(readBuffer, 0, n);
//Debug.Log(n + " " + sp.ReadBufferSize);
//if ((n == 10)) //用来判断是否符合接收长度,不符合则继续接收,因为Read()是分多次从缓冲区读取。
//就可能出现一次读一位一次读12位的情况。
//{
// index = 0;
// for (int i = 0; i < n; i++)
// {
// if (index >= n) index = n - 1;
// data[index] = readBuffer[i]; //将数据存入data中
// index++;
// }
//}
//上面注释的部分为之前的弱校验,只是对接收缓存区内容大小做了一次判断
//下面为多次校验版本,通过查找“发送报文”的帧头“0xF4”来进行拷贝
//建议可以再这里多做几次校验。通过更改报文来确定收到的数据可靠
//CRC、长度、和等各种校验方式,这里仅对校验帧头帧尾举例。
for (int i = 0; i < readBuffer.Length; i++)
{
if (readBuffer[i] == 0xF4)
{
// 检查后面第10位是否为0xEF
if (i + 10 < readBuffer.Length && readBuffer[i + 10] == 0xEF)
{
startIndex = i;
break;
}
}
}
//当startIndex不为-1的时候表示找到,并将从此开始的整个报文拷贝到这里
if (startIndex != -1)
{
Array.Copy(readBuffer, startIndex, data, 0, 10);
}
//打印输入
string str = "";
for (int i = 0; i < index; i++)
{
str += Convert.ToString(data[i]) + " ";
}
Debug.Log(str + " " + n + " " + index + " " + sp.ReadBufferSize);
str = "";
DataProcessingFunction(data);//数据处理函数,这个是根据自己需要怎么处理来写的,在这我就不献丑了。
}
}
catch (Exception ex)
{
Debug.Log(ex);
}
}
Thread.Sleep(100);
}
写打开串口函数OpenPort()
给五个参数赋上初值
SerialPort sp = null;//串口控制
Thread dataReceiveThread;//定义一个线程
//给五个参数赋上初值
portName = COM1;
baudRate = 9600;
parity = Parity.None;
dataBits = 8;
stopBits = StopBits.None;
//将参数传给sp
sp = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
sp.ReadTimeout = 400;
//使用try...catch...结构可以捕捉中途是否出现中断以及导致中断的原因。
try
{
sp.Open();
}
catch (Exception ex)
{
Debug.Log(ex.Message);
}
//当串口打开后会报告一下打开串口信息,这个可以之后引入操作界面以供观察。
if (sp.IsOpen)
{
Debug.Log("打开串口" + portName + " " + baudRate + " " + parity + " " + dataBits + " " + stopBits);
//同时单独打开一个数据接收线程为
dataReceiveThread = new Thread(new ThreadStart(DataReceiveFunction));//数据接收线程
dataReceiveThread.Start();
}
关闭串口ClosePort()
//同打开串口一样也使用try...catch...
try
{
sp.Close();
}
catch (Exception ex)
{
Debug.Log(ex.Message);
}
发送数据WriteData(byte[] bys)
if (sp.IsOpen)
{
sp.Write(bys, 0, bys.Length);
}
对于PLC端参考自由口协议向上方式即可,未来遇到什么古怪问题会持续更新。