【Unity】使用 C# SerialPort 进行串口通信

索引

  • 一、SerialPort串口通信
  • 二、使用SerialPort
    • 1.创建SerialPort对象,进行基本配置
    • 2.写入串口数据
      • ①.写入串口数据的方法
      • ②.封装数据
    • 3.读取串口数据
      • ①.读取串口数据的方法
      • ②.解析数据
    • 4.读取串口数据的时机
      • ①.DataReceived事件
      • ②.多线程接收数据
    • 5.粘包问题处理

一、SerialPort串口通信

C#中的SerialPort类是.NET框架提供的一个用于串口通信的强大工具,主要用于实现计算机与外部设备(如传感器、嵌入式设备、PLC等)之间的数据交换。

二、使用SerialPort

1.创建SerialPort对象,进行基本配置

使用SerialPort时,直接创建一个SerialPort对象即可,其基本配置项包括:

PortName:串口号,如COM1。
BaudRate:波特率,如9600或115200。
DataBits:数据位,通常为7或8。
StopBits:停止位,通常为1、1.5或2。
Parity:奇偶校验位,如None(无校验)、Odd(奇校验)或Even(偶校验)。

代码如下:

    /// <summary>/// 串口号/// </summary>[Label("串口号")] public string portName;/// <summary>/// 波特率/// </summary>[Label("波特率")] public int baudRate;/// <summary>/// 数据位/// </summary>[Label("数据位")] public int dataBits;/// <summary>/// 停止位/// </summary>[Label("停止位")] public StopBits stopBits;/// <summary>/// 奇偶校验位/// </summary>[Label("奇偶校验位")] public Parity parity;private SerialPort _serialPort;protected override void Awake(){base.Awake();_serialPort = new SerialPort();_serialPort.PortName = portName;_serialPort.BaudRate = baudRate;_serialPort.DataBits = dataBits;_serialPort.StopBits = stopBits;_serialPort.Parity = parity;try{_serialPort.Open();}catch (Exception e){Log.Error(e.Message);}}

2.写入串口数据

①.写入串口数据的方法

写入串口数据的方法非常简单,如下:

		//将数据封装为字节数组(可以直接强转字符串,也可以按16进制处理等)byte[] bytes = EncapsulatePackage(data);_serialPort.Write(bytes, 0, bytes.Length);

②.封装数据

如何封装数据取决于数据交换的协议,比如直接强转字符串:

	    /// <summary>/// 单个数据包结束符(换行符)/// </summary>private const byte EndSign = 10;/// <summary>/// 封装数据包(这里要求所有字符必须为ASCII码,也即是一个字符只占一个字节)/// </summary>/// <param name="data">原始数据</param>/// <returns>数据包</returns>private byte[] EncapsulatePackage(string data){byte[] bytes = new byte[data.Length + 1];for (int i = 0; i < data.Length; i++){bytes[i] = (byte)data[i];}//在每个数据包后面补充【结束符】,表明此数据包结束bytes[bytes.Length - 1] = EndSign;return bytes;}

这里以简单协议进行讲解(每个数据包以【换行符】代表结束符)。

3.读取串口数据

①.读取串口数据的方法

读取串口数据的方法非常简单,如下:

		//建立数据缓冲区(大小为串口中可读取数据大小:_serialPort.BytesToRead)byte[] bytes = new byte[_serialPort.BytesToRead];_serialPort.Read(bytes, 0, bytes.Length);//解析数据包string data = AnalyzePackage(bytes);

②.解析数据

如何解析数据取决于数据交换的协议,比如直接强转字符串:

        /// <summary>/// 解析数据包/// </summary>/// <param name="bytes">数据包</param>/// <returns>数据</returns>private string AnalyzePackage(byte[] bytes){return Encoding.Default.GetString(bytes);}

4.读取串口数据的时机

那么对如上的简单的写入、读取串口数据有了基本的了解之后,接下来便是相对不那么简单的部分了。

首先,写入串口数据的时机由我们说了算,何时调用便何时写入,这点毋容置疑。

但是,读取串口数据的时机该是何时?参考下面两种方式。

①.DataReceived事件

SerialPort的DataReceived事件当对象对应的串口接收到了数据时便会触发,用他来读取数据简直不要太丝滑

	    protected override void Awake(){base.Awake();//......_serialPort.DataReceived += OnDataReceived;//......}private void OnDataReceived(object sender, SerialDataReceivedEventArgs e){if (_serialPort.IsOpen && _serialPort.BytesToRead > 0){//建立数据缓冲区(大小为串口中可读取数据大小:_serialPort.BytesToRead)byte[] bytes = new byte[_serialPort.BytesToRead];_serialPort.Read(bytes, 0, bytes.Length);//解析数据包string data = AnalyzePackage(bytes);}}

但是,这里必须得有一个但是,只要在Unity中用过SerialPort的都会知道DataReceived这玩意他不起作用,主要原因如下(别怀疑,就是问的AI):

1.Unity引擎对System.IO.Ports命名空间的支持有限,尤其是对DataReceived事件的支持存在缺陷。在Unity中,DataReceived事件通常不会像在常规C#项目中那样正常触发,这主要是因为Unity的运行环境与普通.NET应用有所不同,特别是在事件处理机制上存在差异。
2.DataReceived事件需要在一个独立的线程中监听串口数据,但在Unity中,主线程(用于游戏逻辑和渲染)和串口数据接收线程之间的同步机制存在问题。因此,DataReceived事件可能无法被正确触发。
3.Unity的Update和FixedUpdate等事件函数运行在主线程中,而串口数据接收通常需要多线程支持。当串口操作在主线程中执行时,可能会因为线程冲突导致DataReceived事件无法触发。

那么,我们不得不考虑更换其他方案了。

②.多线程接收数据

老样子,像处理Socket通信那样,多线程永远是最强的利器:

    private Thread _receiveThread;protected override void Awake(){base.Awake();//新建一个线程,启动数据接收方法ReceivedData_receiveThread = new Thread(new ThreadStart(ReceivedData));_receiveThread.Start();}protected override void OnDestroy(){base.OnDestroy();_receiveThread.Abort();_receiveThread = null;}/// <summary>/// 从串口接收数据/// </summary>private void ReceivedData(){while (true){if (_serialPort.IsOpen && _serialPort.BytesToRead > 0){try{//建立数据缓冲区(大小为串口中可读取数据大小:_serialPort.BytesToRead)byte[] bytes = new byte[_serialPort.BytesToRead];_serialPort.Read(bytes, 0, bytes.Length);//解析数据包string data = AnalyzePackage(bytes);//......}catch (Exception e){Log.Error(e.Message);}}}}

5.粘包问题处理

当然,到此时我们并不能高枕无忧,还有另一个在数据交换领域普遍存在的问题需要我们解决,那就是数据的粘包问题。

粘包问题是指多个数据包在接收端被错误地合并成一个数据包,导致接收方无法正确区分每个数据包的边界。这通常发生在连续发送多个数据包时,接收端来不及解析或缓冲区管理不当的情况下。

就像Socket通信一样,发送方发出的数据是A03568,但接收方可能会分多次接收到,比如2次才接收完,那就可能是A03568,接收方只是一个机器不是人,自然不知道这2个包该连起来合成一个包,那么后续的处理自然就乱套了。

这就是通信协议存在的必要了,以我们的简单通信协议为例(每个数据包以【换行符】代表结束符),在读取数据的方法中进行防粘包处理:

    private byte[] _buffer = new byte[16];private List<byte> _receiveBuffer = new List<byte>();/// <summary>/// 从串口接收数据/// </summary>private void ReceivedData(){while (true){if (_serialPort.IsOpen && _serialPort.BytesToRead > 0){try{//读取一次数据(最大不超过缓冲区大小)int count = _serialPort.Read(_buffer, 0, Mathf.Min(_buffer.Length, _serialPort.BytesToRead));for (int i = 0; i < count; i++){//如果为结束符,则代表一个数据包接收完成,解析该包if (_buffer[i] == EndSign){string data = AnalyzePackage(_receiveBuffer.ToArray());//清空缓冲区_receiveBuffer.Clear();//处理数据HandlerData(data);Log.Info($"接收串口数据:{data}");}//否则加入数据缓冲区else{_receiveBuffer.Add(_buffer[i]);}}}catch (Exception e){Log.Error(e.Message);}}}}

代码很简单,相信处理过字节流的人应该都能看懂,事实上在串口通信中如上的简单通信协议已能胜任大多数情况。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/web/83855.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何写好单元测试:Mock 脱离数据库,告别 @SpringBootTest 的重型启动

如何写好单元测试&#xff1a;Mock 脱离数据库&#xff0c;告别 SpringBootTest 的重型启动 作者&#xff1a;Killian&#xff08;重庆&#xff09; — 欢迎各位架构猎头、技术布道者联系我&#xff0c;项目实战丰富&#xff0c;代码稳健&#xff0c;Mock测试爱好者。 技术栈&a…

【DNS】在 Windows 下修改 `hosts` 文件

在 Windows 下修改 hosts 文件&#xff0c;一般用于本地 DNS 覆盖。操作步骤如下&#xff08;以 Windows 10/11 为例&#xff09;&#xff1a; 1. 以管理员权限打开记事本 点击 开始 → 输入 “记事本”在“记事本”图标上右键 → 选择 以管理员身份运行 如果提示“是否允许此…

共享内存实现进程通信

目录 system V共享内存 共享内存示意图 共享内存函数 shmget函数 shmat函数 shmdt函数 shmctl函数 代码示例 shm头文件 构造函数 获取key值 创建者的构造方式 GetShmHelper 函数 GetShmUseCreate 函数 使用者的构造方式 GetShmForUse 函数 分离附加操作 DetachShm 函数 AttachS…

6月15日星期日早报简报微语报早读

6月15日星期日&#xff0c;农历五月二十&#xff0c;早报#微语早读。 1、证监会拟修订期货公司分类评价&#xff1a;明确扣分标准&#xff0c;优化加分标准&#xff1b; 2、国家考古遗址公园再添10家&#xff0c;全国已评定65家&#xff1b; 3、北京多所高校禁用罗马仕充电宝…

破解关键领域软件测试“三重难题”:安全、复杂性、保密性

在国家关键领域&#xff0c;软件系统正成为核心战斗力的一部分。相比通用软件&#xff0c;关键领域软件在 安全性、复杂性、实时性、保密性 等方面要求极高。如何保障安全合规前提下提升测试效率&#xff0c;确保系统稳定&#xff0c;已成为软件质量保障的核心挑战。 关键领域…

记录一次 Oracle DG 异常停库问题解决过程

记录一次 Oracle DG 异常停库问题解决过程 某医院有以下架构的双节点 Oracle 集群&#xff1a; 节点1:172.16.20.2 节点2:172.16.20.3 SCAN IP&#xff1a;172.16.20.1 DG&#xff1a;172.16.20.1206月12日&#xff0c;医院信息科用户反映无法连接 DG 服务器。 登录 DG 服务…

MySQL使用EXPLAIN命令查看SQL的执行计划

1‌、EXPLAIN 的语法 MySQL 中的 EXPLAIN 命令是用于分析 SQL 查询执行计划的关键工具,它能帮助开发者理解查询的执行方式并找出性能瓶颈‌‌。 语法格式: EXPLAIN <sql语句> 【示例】查询学生表关联班级表的执行计划。 (1)创建班级信息表和学生信息表,并创建索…

Go语言2个协程交替打印

WaitGroup 无缓冲channel waitgroup 用来控制2个协程 Add() 、Done()、Wait() channel用来实现信号的传递和信号的打印 ch1: 用来记录打印的信号 ch2:用来实现信号的传递&#xff0c;实现2个协程的顺序打印 package mainimport ("fmt""sync" )func ma…

微信小程序 路由跳转

路由方式 官方参考文档 wx.switchTab 实现底部导航栏 1.配置信息 app.json"tabBar": {"custom": true,"list": [{"pagePath": "pages/home/index","text": "首页"},{"pagePath": "p…

[Java 基础]正则表达式

正则表达式是一种强大的文本模式匹配工具&#xff0c;它使用一种特殊的语法来描述要搜索或操作的字符串模式。在 Java 中&#xff0c;我们可以使用 java.util.regex包提供的类来处理正则表达式。 :::color3 正则表达式不止 Java 语言提供了相应的功能&#xff0c;很多其他语言…

ArcGIS安装出现1606错误解决办法

问题背景&#xff1a; 由于最近Arcgis10.2打是有些功能不正常退出&#xff0c;比如arctoolbox中的&#xff0c;table to excel 功能&#xff0c;只要一点击&#xff0c;arcgis就报错退出&#xff0c;平常在使用过程中&#xff0c;也经常出现一些莫名其妙的崩溃现象&#xff0c…

wpf 解决DataGridTemplateColumn中width绑定失效问题

感谢酪酪烤奶 提供的Solution 文章目录 感谢酪酪烤奶 提供的Solution使用示例示例代码分析各类交互流程 WPF DataGrid 列宽绑定机制分析整体架构数据流分析1. ViewModel到Slider的绑定2. ViewModel到DataGrid列的绑定a. 绑定代理(BindingProxy)b. 列宽绑定c. 数据流 关键机制详…

语音转文本ASR、文本转语音TTS

ASR Automatic Speech Recognition&#xff0c;语音转文本。 技术难点&#xff1a; 声学多样性 口音、方言、语速、背景噪声会影响识别准确性&#xff1b;多人对话场景&#xff08;如会议录音&#xff09;需要区分说话人并分离语音。 语言模型适配 专业术语或网络新词需要动…

通用embedding模型和通用reranker模型,观测调研

调研Qwen3-Embedding和Qwen3-Reranker 现在有一个的问答库&#xff0c;包括150个QA-pair&#xff0c;用10个query去同时检索问答库的300个questionanswer Embedding模型&#xff0c;query-question的匹配分数 普遍高于 query-answer的匹配分数。比如对于10个query&#xff0c…

基于YOLOv8+Deepface的人脸检测与识别系统

摘要 人脸检测与识别系统是一个集成了先进计算机视觉技术的应用&#xff0c;通过深度学习模型实现人脸检测、识别和管理功能。系统采用双模式架构&#xff1a; ​​注册模式​​&#xff1a;检测新人脸并添加到数据库​​删除模式​​&#xff1a;识别数据库中的人脸并移除匹…

Grdle版本与Android Gradle Plugin版本, Android Studio对应关系

Grdle版本与Android Gradle Plugin版本&#xff0c; Android Studio对应关系 各个 Android Gradle 插件版本所需的 Gradle 版本&#xff1a; https://developer.android.com/build/releases/gradle-plugin?hlzh-cn Maven上发布的Android Gradle Plugin&#xff08;AGP&#x…

用c语言实现简易c语言扫雷游戏

void test(void) {int input 0;do{menu();printf("请选择&#xff1a; >");scanf("%d", &input);switch (input){menu();case 1:printf("扫雷\n");game();break;case 2:printf("退出游戏\n");break;default:printf("输入…

系统辨识的研究生水平读书报告期末作业参考

这是一份关于系统辨识的研究生水平读书报告&#xff0c;内容系统完整、逻辑性强&#xff0c;并深入探讨了理论、方法与实际应用。报告字数超过6000字 从理论到实践&#xff1a;系统辨识的核心思想、方法论与前沿挑战 摘要 系统辨识作为连接理论模型与客观世界的桥梁&#xff…

开源、免费、美观的 Vue 后台管理系统模板

随着前端技术的不断发展&#xff0c;Vue.js 凭借其轻量、高效、易上手的特性&#xff0c;成为国内外开发者最喜爱的前端框架之一。在构建后台管理系统时&#xff0c;Vue 提供了以下优势&#xff1a; 响应式数据绑定&#xff1a;让页面和数据保持同步&#xff0c;开发效率高。 …

适合 Acrobat DC 文件类型解析

文件类型 (File Type)ProgID (Continuous)ProgID (Classic)主要用途.pdfAcroExch.Document.DCAcroExch.Document.20XX (版本特定)Adobe PDF文档格式&#xff0c;用于存储文档内容和格式.pdfxmlAcroExch.pdfxmlAcroExch.pdfxmlPDF与XML结合的格式&#xff0c;可能用于结构化数据…