首页 > 其他分享 >【摘译+整理】System.IO.Ports.SerialPort使用注意

【摘译+整理】System.IO.Ports.SerialPort使用注意

时间:2024-07-26 09:10:12浏览次数:13  
标签:buffer NET System SerialPort BytesToRead DataReceived byte 摘译 port

远古的一篇博客,内容散落于博文和评论 https://sparxeng.com/blog/software/must-use-net-system-io-ports-serialport

C# 和 .NET Framework 提供了一种快速的应用程序开发,非常适合需要随着硬件设计的发展跟踪不断变化的需求的早期开发。在大多数方面都很理想。但.NET 附带的 System.IO.Ports.SerialPort 类是一个明显的例外。委婉地说,它是由计算机科学家设计的,远远超出了他们的核心能力领域。他们既不了解串行通信的特征,也不了解常见的用例。在发布之前,它也不可能在任何真实场景中进行测试,而不会发现漏洞,这些缺陷会使 System.IO.Ports.SerialPort (以下简称IOPSP)的可靠通信成为真正的噩梦。(StackOverflow 上的大量证据证明了这一点)。

更令人惊讶的是,当底层kernel32.dll API 非常好时,还会发生这种级别的问题(我在使用 .NET 之前使用过 WinAPI)。.NET 工程师不仅没有设计出合理的接口,还选择无视非常成熟的 WinAPI 设计,也没有从二十年的内核团队串行移植经验中吸取教训。

下面列出其可靠和不可靠的成员列表:

  • event DataReceived (100%冗余,也完全不可靠)
  • BytesToRead 属性 (完全不可靠)
  • ReadReadExistingReadLine 函数(处理错误完全错误,并且是同步的)
  • PinChanged event (顺序完全不能保证)

可以安全使用的列表:

  • 属性: BaudRate 、 DataBits 、 Parity 、 StopBits ,但仅在打开端口之前。并且仅适用于标准波特率。
  • Handshake属性
  • 构造函数、 PortName属性、 Open函数 IsOpen函数、 GetPortNames函数

还有一个没人使用的成员,因为 MSDN 没有给出任何示例,但对你绝对是必不可少的

  • BaseStream

唯一正常工作的串行端口读取方法是通过 BaseStream 访问。

以下示例是接收数据的错误方式:

port.DataReceived += port_DataReceived;

// (later, in DataReceived event)
try {
    byte[] buffer = new byte[port.BytesToRead];
    port.Read(buffer, 0, buffer.Length);
    raiseAppSerialDataEvent(buffer);
}
catch (IOException exc) {
    handleAppSerialError(exc);
}

下面是正确的方法,它与基础 Win32 API 的使用方式相匹配:

byte[] buffer = new byte[blockLimit];
Action kickoffRead = null;
kickoffRead = delegate {
    port.BaseStream.BeginRead(buffer, 0, buffer.Length, delegate (IAsyncResult ar) {
        try {
            int actualLength = port.BaseStream.EndRead(ar);
            byte[] received = new byte[actualLength];
            Buffer.BlockCopy(buffer, 0, received, 0, actualLength);
            raiseAppSerialDataEvent(received);
        }
        catch (IOException exc) {
            handleAppSerialError(exc);
        }
        kickoffRead();
    }, null);
};
kickoffRead();

从 .NET 4.5 开始,可以改为调用 ReadAsync BaseStream 对象,该对象在内部调用 BeginRead 和 EndRead 。

或者就直接调用 Win32 API的方式。

第一个例子的问题在于:

第一个也是最严重的是 DataReceived 在线程池线程上触发,并且可以再次触发,而无需等待上一个事件处理程序返回。因此,它会导致你进入竞争条件,当你去读取缓冲区时,比 BytesToRead 承诺的要少,因为事件处理程序的另一个实例同时读取它们。应用程序程序员可以通过显式同步来克服这个问题。

但同步不会解决实现本身中存在的竞争条件。BytesToRead 调用 ClearCommError 以获取缓冲区级别,并丢弃其他所有内容。但是 ClearCommError 是对串口寄存器错误标志位的原子交换——你只会看到一次,然后就会清除他们。框架中正在查看这些标志的其他代码以触发 PinChangedErrorReceived 事件由于 BytesToRead(查看并清除寄存器) 会忽略它们,因此事件会丢失。事实上,ErrorReceived 事件的 MSDN 页面显示“由于操作系统决定是否引发此事件,因此不会报告所有奇偶校验错误。这是一个彻头彻尾的谎言 — 事件丢失发生在 BytesToReadBytesToWrite 的 getter 函数中。

译者注:这段原因写在作者和游客的讨论中,感兴趣的可以看看原文。如果写过STM32就能理解作者的意思(STM32的有些寄存器在读后就会清除,这样就会导致丢失了一些事件)。作者表示如果你对串口通信的要求很高,(高性能/低时延/错误等)你就应该使用Win32 API或者 p/invoke 或 C++/CLI。(作者列了写商业库说也能用,感兴趣的在原文评论区自行找一下)。如果你对串口通信的要求不高,其实DataReceived的方式其实也能用。

标签:buffer,NET,System,SerialPort,BytesToRead,DataReceived,byte,摘译,port
From: https://www.cnblogs.com/zhangchen-trunk/p/18324550

相关文章

  • t4模板无法加载文件或程序集system.runtime
        在.net6.0环境下使用T4模板生成代码报错错误正在运行转换:System.IO.FileNotFoundException:未能加载文件或程序集“System.Runtime,Version=6.0.0.0,Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a”或它的某一个依赖项。系统找不到指定的文件。......
  • 【unity实战】完美的2D横版平台跳跃玩家控制器,使用InputSystem+有限状态机实现人物加
    最终效果文章目录最终效果前言素材目录结构动画配置检测脚本状态机玩家有限状态机玩家控制脚本定义人物不同状态待机移动跳跃下落状态落地状态墙壁滑行状态蹬墙跳状态蹬墙跳下落状态一段近战攻击状态二段近战攻击状态冲锋状态土狼时间状态攀爬开始状态攀爬进行状态功能......
  • system.getproperty值从哪里来的
    system.getproperty值从哪里来的在Java中,System.getProperty(Stringkey)方法用于获取系统属性的值。这些系统属性来源于多个地方,但主要可以分为以下几类:JVM启动参数:当JVM启动时,可以通过-D参数来设置系统属性。例如,java-DmyProperty=myValueMyApp会在MyApp中设置一个名为my......
  • Unity ECS System在什么时候更新?如何自定义这个更新的时机?
    在什么时候更新?在其他用户代码都执行完之后。去Netcode的ClientServerBootstrap里可以找到CreateLocalWorld函数,里面有类似这样的代码:publicstaticWorldCreateLocalWorld(stringdefaultWorldName="DefaultWorld"){varworld=newWorld(defaultWorldName,WorldF......
  • systemd service 配置 ulimit 限制
      在bash中,有个ulimit命令,提供了对shell及该shell启动的进程的可用资源控制。主要包括打开文件描述符数量、用户的最大进程数量、coredump文件的大小等。在CentOS5/6等版本中,资源限制的配置可以在/etc/security/limits.conf设置,针对root/user等各个用户或者......
  • 使用 os.system() 命令打开 Streamlit
    我正在工作中在Streamlit中创建一个应用程序。因为大多数人对编程一无所知(包括如何启动Streamlit),所以我想用启动Streamlit的代码创建一个可执行文件。这是我的代码:importoscmd='streamlitrunmain.py'os.system(cmd)接下来我使用pyinstaller来更改它到......
  • Android 13 大屏显示时关于SystemUI和Launcher3问题
    当系统运行在大屏上时,原来显示SystemUI导航栏的位置会变成Launcher3的任务栏,然后导航栏的3个按键显示靠右下角显示1.先看SystemUI的导航栏为什么会消失,移动/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.javapublicvoidcreateNavigationBar......
  • Robot Operating System——借用内存型消息
    大纲功能和工作原理源码分析POD特点POD类型的优点非POD特点生成并发布“借用内存型消息”POD类型非POD类型在ROS2中,"loanedmessage"是一种消息传递机制,用于在发布者(publisher)和订阅者(subscriber)之间传递数据。它是一种高效的消息传递方式,可以避免不必要的数据......
  • 在实际应用中,systemverilog相比vefilog2000有哪些重大的提升
    SystemVerilog相较于Verilog-2000有多项重大提升,这些提升使得SystemVerilog成为更强大的硬件描述和验证语言。以下是一些关键的改进:数据类型扩展:SystemVerilog引入了 logic 数据类型,可以替代Verilog-2000中的 wire 和 reg 类型,提供更灵活的使用方式。支持更广......
  • Distilling System 2 into System 1
    本文是LLM系列文章,针对《DistillingSystem2intoSystem1》的翻译。将系统2蒸馏成系统1摘要1引言2相关工作3将系统2蒸馏到系统14实验5结论6局限性摘要大型语言模型(LLM)可以在推理过程中花费额外的计算来生成中间思想,这有助于产生更好的最终响应。自......