C#串口连接的读取和发送详解
一、串口连接的打开与关闭
串口,即COM口,在.NET中使用 SerialPort 类进行操作。串口开启与关闭,是涉及慢速硬件的IO操作,频繁打开或关闭会影响整体处理速度,甚至导致打开或关闭串口失败。非特殊情况,串口一次性打开后,在退出程序时关闭串口即可。在打开串口前,可以设置一些常用的参数。常用的参数如下:

(1)串口的接受/发送超时时间:ReadTimeout/WriteTimeout。

(2) 串口的接受/发送缓存区大小:ReadBufferSize/WriteBufferSize。

具体代码如下:

// Open Com_serialPort = new SerialPort(com, baud);if (_serialPort.IsOpen) _serialPort.Close();// Set the read / write timeouts_serialPort.ReadTimeout = 500;_serialPort.WriteTimeout = 500;// Set read / write buffer Size,the default of value is 1MB_serialPort.ReadBufferSize = 1024 * 1024;_serialPort.WriteBufferSize = 1024 * 1024;_serialPort.Open();// Discard Buffer_serialPort.DiscardInBuffer();_serialPort.DiscardOutBuffer();

二、串口发送
SerialPort 类发送支持二进制发送与文本发送,需要注意的是文本发送时,需要知道转换的规则,一般常用的是ASCII、UTF7、UTF-8、UNICODE、UTF32。具体代码如下
需要注意的是超出缓冲区的部分会被直接丢弃。因此,如果需要使用串口传送大文件,那接收方和发送方都需要将各自的缓冲区域设置的足够大,以便能够一次性存储下大文件的二进制数组。若条件限制,缓冲区域不能设置过大,那就需要在发送大文件的时候按照发送缓冲区大小分包去发送,接收方按顺序把该数组组合起来形成接受文件的二进制数组。

#region Send/// <summary>/// 发送消息(byte数组)/// </summary>/// <param name="buffer"></param>/// <param name="offset"></param>/// <param name="count"></param>public void Send(byte[] buffer, int offset, int count){lock (_mux){_serialPort.Write(buffer, offset, count);_sendCount += (count - offset);}}/// <summary>/// 发送消息(字符串)/// </summary>/// <param name="encoding">字符串编码方式,具体方式见<see cref="Encoding"/></param>/// <param name="message"></param>public void Send(Encoding encoding , string message){lock (_mux){var buffer = encoding.GetBytes(message);_serialPort.Write(buffer, 0, buffer.Length);_sendCount += buffer.Length;}}#endregion

三、串口接收
串口接收需要注意,消息接受与消息处理要代码分离。不能把流程处理的代码放入信息接受处,因为消息处理或多或少会有耗时,这会造成当发送方发送过快时,接受方的接受缓冲区会缓存多条消息。我们可以把接受到的消息放入队列中,然后在外部线程中,尝试去拿出该条消息进行消费。采用 “生产-消费”模式。具体代码如下:

#region Receiveprivate void PushMessage(){_serialPort.DataReceived += (sender, e) =>{lock (_mux){if (_serialPort.IsOpen == false) return;int length = _serialPort.BytesToRead;byte[] buffer = new byte[length];_serialPort.Read(buffer, 0, length);_receiveCount += length;_messageQueue.Enqueue(buffer);_messageWaitHandle.Set();}};}/// <summary>/// 获取串口接受到的内容/// </summary>/// <param name="millisecondsToTimeout">取消息的超时时间</param>/// <returns>返回byte数组</returns>public byte[] TryMessage(int millisecondsToTimeout = -1){if (_messageQueue.TryDequeue(out var message)){return message;}if (_messageWaitHandle.WaitOne(millisecondsToTimeout)){if (_messageQueue.TryDequeue(out message)){return message;}}return default;}#endregion

四、完整代码与测试结果

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;namespace SerialportDemo
{public class SSerialPort{private SerialPort _serialPort;private readonly ConcurrentQueue<byte[]> _messageQueue;private readonly EventWaitHandle _messageWaitHandle;private int _receiveCount, _sendCount;private readonly object _mux;public int ReceiveCount{get => _receiveCount;}public  int SendCount{get => _sendCount;}public SSerialPort(string com, int baud ){// initialized_mux=new object();_receiveCount = 0;_sendCount = 0;_messageQueue = new ConcurrentQueue<byte[]>();_messageWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);// Open ComOpenCom(com.ToUpper(),baud);// Receive bytePushMessage();}private void OpenCom(string com, int baud){// Open Com_serialPort = new SerialPort(com, baud);if (_serialPort.IsOpen) _serialPort.Close();// Set the read / write timeouts_serialPort.ReadTimeout = 500;_serialPort.WriteTimeout = 500;// Set read / write buffer Size,the default of value is 1MB_serialPort.ReadBufferSize = 1024 * 1024;_serialPort.WriteBufferSize = 1024 * 1024;_serialPort.Open();// Discard Buffer_serialPort.DiscardInBuffer();_serialPort.DiscardOutBuffer();}#region Static/// <summary>/// 获取当前计算机的串行端口名的数组/// </summary>/// <returns></returns>public static string[] GetPortNames(){return SerialPort.GetPortNames();}#endregion#region Receiveprivate void PushMessage(){_serialPort.DataReceived += (sender, e) =>{lock (_mux){if (_serialPort.IsOpen == false) return;int length = _serialPort.BytesToRead;byte[] buffer = new byte[length];_serialPort.Read(buffer, 0, length);_receiveCount += length;_messageQueue.Enqueue(buffer);_messageWaitHandle.Set();}};}/// <summary>/// 获取串口接受到的内容/// </summary>/// <param name="millisecondsToTimeout">取消息的超时时间</param>/// <returns>返回byte数组</returns>public byte[] TryMessage(int millisecondsToTimeout = -1){if (_messageQueue.TryDequeue(out var message)){return message;}if (_messageWaitHandle.WaitOne(millisecondsToTimeout)){if (_messageQueue.TryDequeue(out message)){return message;}}return default;}#endregion#region Send/// <summary>/// 发送消息(byte数组)/// </summary>/// <param name="buffer"></param>/// <param name="offset"></param>/// <param name="count"></param>public void Send(byte[] buffer, int offset, int count){lock (_mux){_serialPort.Write(buffer, offset, count);_sendCount += (count - offset);}}/// <summary>/// 发送消息(字符串)/// </summary>/// <param name="encoding">字符串编码方式,具体方式见<see cref="Encoding"/></param>/// <param name="message"></param>public void Send(Encoding encoding , string message){lock (_mux){var buffer = encoding.GetBytes(message);_serialPort.Write(buffer, 0, buffer.Length);_sendCount += buffer.Length;}}#endregion/// <summary>/// 清空接受/发送总数统计/// </summary>public void ClearCount(){lock (_mux){_sendCount = 0;_receiveCount = 0;}}/// <summary>/// 关闭串口/// </summary>public void Close(){_serialPort.Close();}}
}

测试代码如下:

class Program{static void Main(string[] args){Console.WriteLine($"该计算机可使用的串口列表:{string.Join(",", SSerialPort.GetPortNames())}");Console.Write("请输入需要打开的串口:");string port = Console.ReadLine();SSerialPort com = new SSerialPort(port, 57600);Console.WriteLine($"串口 {port} 打开成功...");Console.Write("请输入需要打开的串口发送的消息:");string text = Console.ReadLine();while (true){com.Send(Encoding.Default, text);Console.WriteLine($"总共发送 {com.SendCount}");var message = com.TryMessage();if (message != null){Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss fff")} {Encoding.Default.GetString(message)}"); TEST:从添加延时可以测试到,接受消息和处理消息必须分不同线程处理。因为对于消息的处理或多或少都需要耗时,这样容易造成消息处理不及时。而添加到队列后,我们可以随时取出处理//System.Threading.Thread.Sleep(100*1);}Console.WriteLine($"总共接受 {com.ReceiveCount}");}Console.ReadKey();}}

使用串口工具测试如下,对于串口的接受如丝般顺滑。当我们在消息中增加测试延时后,就会发现当串口工具继续快速发送一段时间后关闭发送,发现使用队列后,依然没有丢失一条来自发送方的消息。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
using System.IO.Ports;
namespace PacketsTest
{class ComSR{private SerialPort serialPort;private bool status = false;//接受结束状态,true为成功接收到数据.private byte[] reciveData = null;//接受到的数据存储到这里.public bool Status{set { status = value; }get { return status; }}public byte[] RecivedData{get { return reciveData; }}public SerialPort _SerialPort{get { return serialPort; }}public ComSR(string portName,int baudRate,int dataBits,Parity parity,StopBits stopbits){serialPort = new SerialPort();serialPort.PortName = portName;//设置端口serialPort.BaudRate = baudRate;//设置波特率serialPort.DataBits = dataBits;//设置数据长度serialPort.Parity = parity;//设置奇偶校验协议serialPort.StopBits = stopbits;//停止位serialPort.ReceivedBytesThreshold = 1;//这里是读事件触发条件,1代表一个字节serialPort.DtrEnable = true;//启用数据终端就绪信号serialPort.RtsEnable = true;//请求发送就绪信号serialPort.DataReceived += new SerialDataReceivedEventHandler(Data_Recive);//绑定处理读事件的函数.}public void SendBuff(byte[] data){try{serialPort.Write(data, 0, data.Length);//这里是写      }catch (Exception e){if (serialPort != null && serialPort.IsOpen){status = false; //这个status是我在上层类中判断端口读写状态的属性,可以去掉serialPort.Close();}}}void Data_Recive(object o, EventArgs e)//这个是读端口,事件处理函数{try{Thread.Sleep(3);int length = serialPort.BytesToRead;reciveData = new byte[length];serialPort.Read(reciveData, 0, length);while (serialPort.BytesToRead != 0)//由于数据长度读写不一,而且时间很难确定,所以在这里做了这样的处理{Thread.Sleep(1);}if (serialPort.IsOpen){status = true;//serialPort.Close();//这里不应该关闭,应当在更高层次的程序上关闭端口.}}catch (Exception ex){if (serialPort != null && serialPort.IsOpen){status = false;serialPort.Close();}}}public void PortOpen(){if (!serialPort.IsOpen){serialPort.Open();}else{serialPort.Close();serialPort.Open();}}/// <summary>/// 关闭端口/// </summary>public void PortClose(){if (serialPort != null && serialPort.IsOpen){serialPort.Close();}}}
}

C#串口连接的读取和发送详解相关推荐

  1. FPGA串口接收与发送详解( part 3 )

    之前的part1~2已经详解完了单个数据的串口接收与发送,链接如下: FPGA串口接收与发送 详解 (part 1 )_居安士的博客-CSDN博客 FPGA串口接收与发送详解( part 2 )_居安 ...

  2. java中属性文件读取案例_java相关:Spring中属性文件properties的读取与使用详解

    java相关:Spring中属性文件properties的读取与使用详解 发布于 2020-6-3| 复制链接 摘记: Spring中属性文件properties的读取与使用详解实际项目中,通常将一些 ...

  3. Android 系统属性读取和设置详解

    Android 系统属性读取和设置详解 一.在adb中进行属性读取和设置 1.Settings Provider设置和读取 获取 设置 2.SystemProperties属性读取和设置 二.Andr ...

  4. http\https的连接过程及数字证书详解

    http\https的连接过程及数字证书详解 内推军p185 http连接过程(相当于输入url会发生什么) 1.域名解析 2.发起TCP的三次握手 3.Web浏览器向服务器发送http请求命令 4. ...

  5. python连接oracle数据库的方法_Python3.6连接Oracle数据库的方法详解

    本文实例讲述了Python3.6连接Oracle数据库的方法.分享给大家供大家参考,具体如下: 下载cx_Oracle模块模块: https://pypi.python.org/pypi/cx_Ora ...

  6. java 远程shell脚本_java通过ssh连接服务器执行shell命令详解及实例

    java通过ssh连接服务器执行shell命令详解 java通过ssh连接服务器执行shell命令:JSch 是SSH2的一个纯Java实现.它允许你连接到一个sshd 服务器,使用端口转发,X11转 ...

  7. DBus glib 各数据类型接收与发送详解—C语言(3)

    DBus glib 各数据类型接收与发送详解-C语言(3) 动机 前置知识 正文 Python 测试服务 使用 C 实现复杂数据类型的传递 DICT_DICT ObjectPath_Dict_Stru ...

  8. php读取大文件详解【OK】

    在php中,对于文件的读取时,最快捷的方式莫过于使用一些诸如file.file_get_contents之类的函数,简简单单的几行代码就能很漂亮 的完成我们所需要的功能.但当所操作的文件是一个比较大的 ...

  9. matlab中读入sav,matlab数据读取与存入详解

    matlab数据读取与存入详解 在编写一个程序时,经常需要从外部读入数据,或者将程序运行的结果保存为文件.MATLAB使用多种格式打开和保存数据.本章将要介绍 MATLAB中文件的读写和数据的导入导出 ...

最新文章

  1. python UnicodeEncodeError 编码错误总结
  2. Spring+Shiro+CAS整合配置笔记
  3. 中国首档程序员真人秀,20位选手找bug做模型,结果一开场主办方就惨遭攻击...
  4. 笔记本电脑按开机键没反应怎么办?(先记得长按开机键,大约10秒钟看看可以吗)
  5. Proteus模拟STM32F103R6微控制器之串口通信USART的方法
  6. js string转number_Node.js 和 C++ 之间的类型转换
  7. linux 系统改名,linux改名命令
  8. logrotate工具日志切割
  9. Openlayer:学习笔记之简单的ol.View应用
  10. DeepFake技术--win7下faceswap环境配置(一)(二)(三)
  11. AR Camera开发记录(三) -- 替换人脸贴图
  12. Max Script|操作材质编辑器
  13. 基于加速度计的倾角检测算法-C语言程序
  14. __mian__的作用
  15. Android 发布代码到github 并且部署到 JitPack maven 仓库详细步骤
  16. SAS卡,RAID卡,HBA卡区别与联系
  17. JS一百只鸡卖一百块钱,公鸡5元,母鸡3元,三只小鸡一元
  18. 学阿里中台,80%的人只学到了皮毛!揭秘阿里中台的12个架构思维和原则
  19. GeoJSON详解(带图)
  20. php框架语法,PHP框架之ThinkPHP框架

热门文章

  1. 美国之行第四天(r12笔记第54天)
  2. Linux mint19系統下安装QQ微信通讯软件
  3. IBM X3650 M/T 7979 安装WIN2012 R2
  4. 举头望明月打计算机术语,关于中秋节谜语大全
  5. 串口发送模块uart_tx详解
  6. 千锋教育实训day04————java
  7. 【车载开发系列】UDS诊断协议总括篇
  8. 《小狗钱钱》书籍摘录
  9. java套娃_[GXYCTF2019]禁止套娃
  10. 基于JAVA康养旅游信息系统计算机毕业设计源码+系统+mysql数据库+lw文档+部署