为您推荐:
精华内容
最热下载
问答
  • 5星
    5.25MB weixin_43096877 2021-05-29 15:37:02
  • 5星
    680KB zb12138 2021-03-15 16:34:59
  • 5星
    43KB ZCY5202015 2021-04-22 14:26:38
  • 5星
    21KB ZCY5202015 2021-04-22 14:21:09
  • 5星
    317.93MB qq_41740659 2021-07-09 11:12:04
  • 5星
    8.29MB qq_44526422 2021-02-19 17:01:30
  • 5星
    65.2MB guoruibin123 2021-05-11 06:06:48
  • 5星
    57KB ailemony 2020-11-30 21:06:04
  • 5星
    5.22MB GJZGRB 2021-03-10 14:25:58
  • 5星
    8.36MB wangliang0633 2020-11-19 15:12:18
  • 1. 下载jpnevulator文件,解压,并进入文件夹 链接: https://pan.baidu.com/s/1Ko1-wAUrWydWvVSYn9qv_Q ... 所以,现在想要监听发送和接收到的数据,你需要连接的是串口/dev/pts/9。发送和接收的消息会在终端显示出来

    1. 下载jpnevulator文件,解压,并进入文件夹

    链接: https://pan.baidu.com/s/1Ko1-wAUrWydWvVSYn9qv_Q 提取码: 326j

    2. linux下载jpnevulator

    sudo apt install jpnevulator

    3. 使用

    这里读取/dev/ttyUSB0发送回来的数据,并显示在终端。注意这时候原先接收此数据的设备就被取代了

    $ jpnevulator --ascii --timing-print --tty /dev/ttyUSB0:mySerial --read 
    $ jpnevulator --ascii --pty=:SerialSent --pass --tty "/dev/ttyUSB0:SerialReceived" --rea

    输入完毕后,终端会有输出

    命令解析:--pty 会首先虚拟一个假的终端/dev/pts/9出来,--pass会把/dev/pts/9上接收到的数据转发到/dev/ttyUSB0

    --read会读取/dev/pts/9收到的和/dev/ttyUSB0从外部收到的,并显示出来。

     

    所以,现在想要监听发送和接收到的数据,你需要连接的是串口/dev/pts/9。发送和接收的消息会在终端显示出来

     

    展开全文
    yaojinjian1995 2021-05-14 17:42:58
  • 第一篇文章我相信很多人不看都能做的出来,但是,用过微软SerialPort类的人,都遇到过这个尴尬,关闭串口的时候会让软件死锁。天哪,我可不是武断,算了。不要太绝对了。99.9%的人吧,都遇到过这个问题。我想只有...

    第一篇文章我相信很多人不看都能做的出来,但是,用过微软SerialPort类的人,都遇到过这个尴尬,关闭串口的时候会让软件死锁。天哪,我可不是武断,算了。不要太绝对了。99.9%的人吧,都遇到过这个问题。我想只有一半的人真的解决了。另外一半的人就睁只眼闭只眼阿弥佗佛希望不要在客户那里出现这问题了。

    你看到我的文章,就放心吧,这问题有救了。我们先回顾一下上一篇中的代码

    voidcomm_DataReceived(objectsender, SerialDataReceivedEventArgs e)

    {

    //先记录下来,避免某种原因,人为的原因,操作几次之间时间长,缓存不一致

    intn = comm.BytesToRead;

    //声明一个临时数组存储当前来的串口数据

    byte[] buf =newbyte[n];

    //增加接收计数

    received_count += n;

    //读取缓冲数据

    comm.Read(buf, 0, n);

    //清除字符串构造器的内容

    builder.Clear();

    //因为要访问ui资源,所以需要使用invoke方式同步ui。

    this.Invoke((EventHandler)(delegate{...界面更新,略}));

    }

    privatevoidbuttonOpenClose_Click(objectsender, EventArgs e)

    {

    //根据当前串口对象,来判断操作

    if(comm.IsOpen)

    {

    //打开时点击,则关闭串口

    comm.Close();//这里就是可能导致软件死掉的地方

    }

    else

    {...}

    }

    void comm_DataReceived(object sender, SerialDataReceivedEventArgs e)

    {

    //先记录下来,避免某种原因,人为的原因,操作几次之间时间长,缓存不一致

    int n = comm.BytesToRead;

    //声明一个临时数组存储当前来的串口数据

    byte[] buf = new byte[n];

    //增加接收计数

    received_count += n;

    //读取缓冲数据

    comm.Read(buf, 0, n);

    //清除字符串构造器的内容

    builder.Clear();

    //因为要访问ui资源,所以需要使用invoke方式同步ui。

    this.Invoke((EventHandler)(delegate{...界面更新,略}));

    }

    private void buttonOpenClose_Click(object sender, EventArgs e)

    {

    //根据当前串口对象,来判断操作

    if (comm.IsOpen)

    {

    //打开时点击,则关闭串口

    comm.Close();//这里就是可能导致软件死掉的地方

    }

    else

    {...}

    }

    为什么会死锁呢,并发冲突。

    我们要了解一下SerialPort的实现和串口通讯机制,在你打开串口的时候,SerialPort会创建一个监听线程ListenThread,在这个线程中,等待注册的串口中断,当收到中断后,会调用DataReceived事件。调用完成后,继续进入循环等待,直到串口被关闭退出线程。

    我们的UI主线程如何做的呢,首先创建一个窗体,然后执行了Application.Run(窗体实例)。是这样把,这里的Application.Run就是创建了一个消息循环,循环的处理相关的消息。

    这里我们就有了2个线程,UI主线程、串口监听线程。那么你在DataReceived处理数据的时候,就需要线程同步,避免并发冲突,什么是并发冲突?并发冲突就是2个或多个并行(至少看上去像)的线程运行的时候,多个线程共同的操作某一线程的资源,在时序上同时或没有按我们的预计顺序操作,这样就可能导致数据混乱无序或是彼此等待完成死锁软件。

    而串口程序大多是后者。为什么呢,看看我们的例子中DataReceived做了什么?首先读取数据,然后就是调用this.Invoke方法更新UI了。这里Invoke的时候,监听线程将等待UI线程的标志,等到后,开始操作UI的资源,当操作完成之前,监听线程也就停在DataReceived方法的调用这里,如果这个时候。并发了关闭串口的操作会如何呢?SerialPort的Close方法,会首先尝试等待和监听线程一样的一个互斥体、临界区、或是事件(不确定.net用的哪种)。那这个同步对象什么时候释放呢?每次循环结束就释放,哦。循环为什么不结束呢?因为这一次的循环操作执行到DataReceived之后,执行了Invoke去更新界面了,那Invoke怎么又没有执行完成呢?看上去很简单的几行代码。虽然我没仔细研读过.net的Invoke原理,但我猜测是通过消息的方式来同步的,这也是为什么这么多的类,只有控件(窗体也是控件的一种,.net在概念上,颠覆了微软自己的概念,传统的win32编程,是说所有的控件都是个window,只是父窗体不同,表现形式不同,但都是基于系统消息队列的,.net出于更高的抽象,正好反过来了。呵呵)才有Invoke方法了。(委托自己的Invoke和这个不同)

    我猜测控件/窗体的Invoke是SendMessage方式实现的,那么发送消息后就会等待消息循环来处理消息了。如果你直接去关闭串口了。你点击按钮本身也会被转换成消息WM_CLICK,消息循环在处理按钮的WM_CLICK时候,调用你按钮的OnClick方法,进而触发调用你的ButtonClose_Click事件,这都是同步调用的,你的主线程,处理消息的过程,停在了这个Click事件,而你的Click事件又去调用了SerialPort的Close方法,Close方法又因为和串口监听线程的同步信号量关联在一起需要等待一次的while结束,而这个while循环中调用了DataReceived方法,这个方法中调用了Invoke,也就是发送了消息到消息队列等待结果,但消息循环正在处理你的关闭按钮事件等待退出。

    实在太复杂了,这个情况下,你想要真的关闭串口成功,就需要while中的DataReceived方法调用结束释放同步信号,就需要执行完Invoke,就需要执行消息循环,幸运的是,我们真的有办法执行消息循环来打破僵局。Application.DoEvents()。还好,不幸中的万幸。可是问题又来了,你能让Invoke结束,但你无法确定是否在你调用消息循环后,你的某一时刻不会再次并发,可能由于单cpu的串行操作模拟并行中,又把时间片先分给了优先级高的串口监听线程呢?是有可能的。所以,我们就需要一点方法来避免再次invoke窗体。优化后不会司机的例子如下,我们修改DataReceived方法,关闭方法,并定义2个标记Listening和Closing。

    usingSystem;

    usingSystem.Collections.Generic;

    usingSystem.ComponentModel;

    usingSystem.Data;

    usingSystem.Drawing;

    usingSystem.Linq;

    usingSystem.Text;

    usingSystem.Windows.Forms;

    usingSystem.IO.Ports;

    usingSystem.Text.RegularExpressions;

    namespaceSerialportSample

    {

    publicpartialclassSerialportSampleForm : Form

    {

    privateSerialPort comm =newSerialPort();

    privateStringBuilder builder =newStringBuilder();//避免在事件处理方法中反复的创建,定义到外面。

    privatelongreceived_count = 0;//接收计数

    privatelongsend_count = 0;//发送计数

    privateboolListening =false;//是否没有执行完invoke相关操作

    privateboolClosing =false;//是否正在关闭串口,执行Application.DoEvents,并阻止再次invoke

    publicSerialportSampleForm()

    {

    InitializeComponent();

    }

    //窗体初始化

    privatevoidForm1_Load(objectsender, EventArgs e)

    {

    //初始化下拉串口名称列表框

    string[] ports = SerialPort.GetPortNames();

    Array.Sort(ports);

    comboPortName.Items.AddRange(ports);

    comboPortName.SelectedIndex = comboPortName.Items.Count > 0 ? 0 : -1;

    comboBaudrate.SelectedIndex = comboBaudrate.Items.IndexOf("9600");

    //初始化SerialPort对象

    comm.NewLine ="/r/n";

    comm.RtsEnable =true;//根据实际情况吧。

    //添加事件注册

    comm.DataReceived += comm_DataReceived;

    }

    voidcomm_DataReceived(objectsender, SerialDataReceivedEventArgs e)

    {

    if(Closing)return;//如果正在关闭,忽略操作,直接返回,尽快的完成串口监听线程的一次循环

    try

    {

    Listening =true;//设置标记,说明我已经开始处理数据,一会儿要使用系统UI的。

    intn = comm.BytesToRead;//先记录下来,避免某种原因,人为的原因,操作几次之间时间长,缓存不一致

    byte[] buf =newbyte[n];//声明一个临时数组存储当前来的串口数据

    received_count += n;//增加接收计数

    comm.Read(buf, 0, n);//读取缓冲数据

    builder.Clear();//清除字符串构造器的内容

    //因为要访问ui资源,所以需要使用invoke方式同步ui。

    this.Invoke((EventHandler)(delegate

    {

    //判断是否是显示为16禁止

    if(checkBoxHexView.Checked)

    {

    //依次的拼接出16进制字符串

    foreach(bytebinbuf)

    {

    builder.Append(b.ToString("X2") +" ");

    }

    }

    else

    {

    //直接按ASCII规则转换成字符串

    builder.Append(Encoding.ASCII.GetString(buf));

    }

    //追加的形式添加到文本框末端,并滚动到最后。

    this.txGet.AppendText(builder.ToString());

    //修改接收计数

    labelGetCount.Text ="Get:"+ received_count.ToString();

    }));

    }

    finally

    {

    Listening =false;//我用完了,ui可以关闭串口了。

    }

    }

    privatevoidbuttonOpenClose_Click(objectsender, EventArgs e)

    {

    //根据当前串口对象,来判断操作

    if(comm.IsOpen)

    {

    Closing =true;

    while(Listening) Application.DoEvents();

    //打开时点击,则关闭串口

    comm.Close();

    Closing =false;

    }

    else

    {

    //关闭时点击,则设置好端口,波特率后打开

    comm.PortName = comboPortName.Text;

    comm.BaudRate =int.Parse(comboBaudrate.Text);

    try

    {

    comm.Open();

    }

    catch(Exception ex)

    {

    //捕获到异常信息,创建一个新的comm对象,之前的不能用了。

    comm =newSerialPort();

    //现实异常信息给客户。

    MessageBox.Show(ex.Message);

    }

    }

    //设置按钮的状态

    buttonOpenClose.Text = comm.IsOpen ?"Close":"Open";

    buttonSend.Enabled = comm.IsOpen;

    }

    //动态的修改获取文本框是否支持自动换行。

    privatevoidcheckBoxNewlineGet_CheckedChanged(objectsender, EventArgs e)

    {

    txGet.WordWrap = checkBoxNewlineGet.Checked;

    }

    privatevoidbuttonSend_Click(objectsender, EventArgs e)

    {

    //定义一个变量,记录发送了几个字节

    intn = 0;

    //16进制发送

    if(checkBoxHexSend.Checked)

    {

    //我们不管规则了。如果写错了一些,我们允许的,只用正则得到有效的十六进制数

    MatchCollection mc = Regex.Matches(txSend.Text, @"(?i)[/da-f]{2}");

    List buf =newList();//填充到这个临时列表中

    //依次添加到列表中

    foreach(Match minmc)

    {

    buf.Add(byte.Parse(m.Value));

    }

    //转换列表为数组后发送

    comm.Write(buf.ToArray(), 0, buf.Count);

    //记录发送的字节数

    n = buf.Count;

    }

    else//ascii编码直接发送

    {

    //包含换行符

    if(checkBoxNewlineSend.Checked)

    {

    comm.WriteLine(txSend.Text);

    n = txSend.Text.Length + 2;

    }

    else//不包含换行符

    {

    comm.Write(txSend.Text);

    n = txSend.Text.Length;

    }

    }

    send_count += n;//累加发送字节数

    labelSendCount.Text ="Send:"+ send_count.ToString();//更新界面

    }

    privatevoidbuttonReset_Click(objectsender, EventArgs e)

    {

    //复位接受和发送的字节数计数器并更新界面。

    send_count = received_count = 0;

    labelGetCount.Text ="Get:0";

    labelSendCount.Text ="Send:0";

    }

    }

    }

    using System;

    using System.Collections.Generic;

    using System.ComponentModel;

    using System.Data;

    using System.Drawing;

    using System.Linq;

    using System.Text;

    using System.Windows.Forms;

    using System.IO.Ports;

    using System.Text.RegularExpressions;

    namespace SerialportSample

    {

    public partial class SerialportSampleForm : Form

    {

    private SerialPort comm = new SerialPort();

    private StringBuilder builder = new StringBuilder();//避免在事件处理方法中反复的创建,定义到外面。

    private long received_count = 0;//接收计数

    private long send_count = 0;//发送计数

    private bool Listening = false;//是否没有执行完invoke相关操作

    private bool Closing = false;//是否正在关闭串口,执行Application.DoEvents,并阻止再次invoke

    public SerialportSampleForm()

    {

    InitializeComponent();

    }

    //窗体初始化

    private void Form1_Load(object sender, EventArgs e)

    {

    //初始化下拉串口名称列表框

    string[] ports = SerialPort.GetPortNames();

    Array.Sort(ports);

    comboPortName.Items.AddRange(ports);

    comboPortName.SelectedIndex = comboPortName.Items.Count > 0 ? 0 : -1;

    comboBaudrate.SelectedIndex = comboBaudrate.Items.IndexOf("9600");

    //初始化SerialPort对象

    comm.NewLine = "/r/n";

    comm.RtsEnable = true;//根据实际情况吧。

    //添加事件注册

    comm.DataReceived += comm_DataReceived;

    }

    void comm_DataReceived(object sender, SerialDataReceivedEventArgs e)

    {

    if (Closing) return;//如果正在关闭,忽略操作,直接返回,尽快的完成串口监听线程的一次循环

    try

    {

    Listening = true;//设置标记,说明我已经开始处理数据,一会儿要使用系统UI的。

    int n = comm.BytesToRead;//先记录下来,避免某种原因,人为的原因,操作几次之间时间长,缓存不一致

    byte[] buf = new byte[n];//声明一个临时数组存储当前来的串口数据

    received_count += n;//增加接收计数

    comm.Read(buf, 0, n);//读取缓冲数据

    builder.Clear();//清除字符串构造器的内容

    //因为要访问ui资源,所以需要使用invoke方式同步ui。

    this.Invoke((EventHandler)(delegate

    {

    //判断是否是显示为16禁止

    if (checkBoxHexView.Checked)

    {

    //依次的拼接出16进制字符串

    foreach (byte b in buf)

    {

    builder.Append(b.ToString("X2") + " ");

    }

    }

    else

    {

    //直接按ASCII规则转换成字符串

    builder.Append(Encoding.ASCII.GetString(buf));

    }

    //追加的形式添加到文本框末端,并滚动到最后。

    this.txGet.AppendText(builder.ToString());

    //修改接收计数

    labelGetCount.Text = "Get:" + received_count.ToString();

    }));

    }

    finally

    {

    Listening = false;//我用完了,ui可以关闭串口了。

    }

    }

    private void buttonOpenClose_Click(object sender, EventArgs e)

    {

    //根据当前串口对象,来判断操作

    if (comm.IsOpen)

    {

    Closing = true;

    while (Listening) Application.DoEvents();

    //打开时点击,则关闭串口

    comm.Close();

    Closing = false;

    }

    else

    {

    //关闭时点击,则设置好端口,波特率后打开

    comm.PortName = comboPortName.Text;

    comm.BaudRate = int.Parse(comboBaudrate.Text);

    try

    {

    comm.Open();

    }

    catch(Exception ex)

    {

    //捕获到异常信息,创建一个新的comm对象,之前的不能用了。

    comm = new SerialPort();

    //现实异常信息给客户。

    MessageBox.Show(ex.Message);

    }

    }

    //设置按钮的状态

    buttonOpenClose.Text = comm.IsOpen ? "Close" : "Open";

    buttonSend.Enabled = comm.IsOpen;

    }

    //动态的修改获取文本框是否支持自动换行。

    private void checkBoxNewlineGet_CheckedChanged(object sender, EventArgs e)

    {

    txGet.WordWrap = checkBoxNewlineGet.Checked;

    }

    private void buttonSend_Click(object sender, EventArgs e)

    {

    //定义一个变量,记录发送了几个字节

    int n = 0;

    //16进制发送

    if (checkBoxHexSend.Checked)

    {

    //我们不管规则了。如果写错了一些,我们允许的,只用正则得到有效的十六进制数

    MatchCollection mc = Regex.Matches(txSend.Text, @"(?i)[/da-f]{2}");

    List buf = new List();//填充到这个临时列表中

    //依次添加到列表中

    foreach (Match m in mc)

    {

    buf.Add(byte.Parse(m.Value));

    }

    //转换列表为数组后发送

    comm.Write(buf.ToArray(), 0, buf.Count);

    //记录发送的字节数

    n = buf.Count;

    }

    else//ascii编码直接发送

    {

    //包含换行符

    if (checkBoxNewlineSend.Checked)

    {

    comm.WriteLine(txSend.Text);

    n = txSend.Text.Length + 2;

    }

    else//不包含换行符

    {

    comm.Write(txSend.Text);

    n = txSend.Text.Length;

    }

    }

    send_count += n;//累加发送字节数

    labelSendCount.Text = "Send:" + send_count.ToString();//更新界面

    }

    private void buttonReset_Click(object sender, EventArgs e)

    {

    //复位接受和发送的字节数计数器并更新界面。

    send_count = received_count = 0;

    labelGetCount.Text = "Get:0";

    labelSendCount.Text = "Send:0";

    }

    }

    }

    至此,不会再出现关闭死锁问题了。

    希望这篇文章能解你的燃眉之急,非常高兴能与读者分享我层遇到,大多数人都遇到的这个问题。如果说的不明白,欢迎讨论。

    后续的有关通讯程序底层设计的文章会讲述一个具有丰富扩展性,但有设计简介的万能通讯库,支持网络、蓝牙、串口通讯、并口通讯。但不要指望我都实现出来了,我只是设计出这个框架。

    //append by wuyazhe @2011-5-26

    上面有一点疏漏,源自第一篇,结果到这里还是没修改,源码中有一行,需要修改一下。

    //发送按钮中

    buf.Add(byte.Parse(m.Value));

    要修改为

    buf.Add(byte.Parse(m.Value,System.Globalization.NumberStyles.HexNumber));

    2010-05-27 09:54 11853人阅读 评论(139)

    我们的串口程序,除了通用的,进行串口监听收发的简单工具,大多都和下位机有关,这就需要关心我们的通讯协议如何缓存,分析,以及通知界面。

    我们先说一下通讯协议。通讯协议就是通讯双方共同遵循的一套规则,定义协议的原则是尽可能的简单以提高传输率,尽可能的具有安全性保证数据传输完整正确。基于这2点规则,我们一个通讯协议应该是这样的:头+数据长度+数据正文+校验

    例如:AA 44 05 01 02 03 04 05 EA

    这里我假设的一条数据,协议如下:

    数据头:     AA 44

    数据长度: 05

    数据正文: 01 02 03 04 05

    校验:       EA

    一般数据的校验,都会采用常用的方式,CRC16,CRC32,Xor。

    有的数据安全要求高的,不允许丢包的,可能还要加入重发机制或是加入数据恢复算法,在校验后根据前面数据添加恢复字节流以恢复数据。我这里采用的是简单的异或校验,包含数据头的所有字节,依次异或得到的。

    协议很简单,我也认为分析协议是很简单的事情,下面我们就如何分析协议来实际的结合c#看一下。

    er…再等等,在我们实际开始编码之前,还有一个规则需要了解,我们有了通讯协议,如何结合串口的协议来分析,需要关心什么呢?哦。一般就是4个问题:缓存收到的所有数据,找到一条完整数据,分析数据,界面通知。

    如果分的更详细一点,缓存收到的所有数据,我们想到最高效的办法就是顺序表,也就是数组,但数组的操作比较复杂,当你使用完一条数据后,用过的需要移除;新数据如果过多的时候,缓存过大需要清理;数据搬移等等,很有可能一个不小心就会丢数据导致软件出些莫名其妙的小问题。个人建议,使用List,内部是数组方式实现,每次数据不足够的时候会扩容1倍,数据的增删改都已经做的很完善了。不会出现什么小问题。

    找到一条完整数据,如何找到完整数据呢?就我们例子的这个协议,首先在缓存的数据中找AA 44,当我们找到后,探测后面的字节,发现是05,然后看缓存剩下的数据是否足够,不足够就不用判断,减少时间消耗,如果剩余数据>=6个(包含1个字节的校验),我们就算一个校验,看和最后的校验是否一致。

    分析数据:鉴于网络的开放性,我无法确定读者对c#的了解程度,介绍一下,常用的方式就是BitConvert.ToInt32这一系列的方法,把连续的字节(和变量长度一样)读取并转换为对应的变量。c++下使用memcpy,或直接类型转换后进行值拷贝,vb6下使用CopyMemory这个api。

    校验:前面说过了。完整性判断的时候需要和校验对比,大多系统都不太严格,不支持重发,所以数据错误就直接丢弃。导致数据错误的原因很多,比如电磁干扰导致数据不完整或错误、硬件驱动效率不够导致数据丢失、我们的软件缓存出错等。这些软件因素数据系统错误,需要修改,但是电磁干扰么,有这个可能的。虽然很少。

    其实我知道,就算是我,看别人的博客也是,喜欢看图片,看代码,文字性的东西,一看就头大。那我接下来贴出基于上一篇文章的改进版本,支持协议分析(协议不能配置,可配置的协议不是我们讨论的范畴。可以看看有DFA(确定性有限状态机))

    我们修改一下界面,以便能显示收到后分析的数据

    代码如下:

    usingSystem;

    usingSystem.Collections.Generic;

    usingSystem.ComponentModel;

    usingSystem.Data;

    usingSystem.Drawing;

    usingSystem.Linq;

    usingSystem.Text;

    usingSystem.Windows.Forms;

    usingSystem.IO.Ports;

    usingSystem.Text.RegularExpressions;

    namespaceSerialportSample

    {

    publicpartialclassSerialportSampleForm : Form

    {

    privateSerialPort comm =newSerialPort();

    privateStringBuilder builder =newStringBuilder();//避免在事件处理方法中反复的创建,定义到外面。

    privatelongreceived_count = 0;//接收计数

    privatelongsend_count = 0;//发送计数

    privateboolListening =false;//是否没有执行完invoke相关操作

    privateboolClosing =false;//是否正在关闭串口,执行Application.DoEvents,并阻止再次invoke

    privateList buffer =newList(4096);//默认分配1页内存,并始终限制不允许超过

    privatebyte[] binary_data_1 =newbyte[9];//AA 44 05 01 02 03 04 05 EA

    publicSerialportSampleForm()

    {

    InitializeComponent();

    }

    //窗体初始化

    privatevoidForm1_Load(objectsender, EventArgs e)

    {

    //初始化下拉串口名称列表框

    string[] ports = SerialPort.GetPortNames();

    Array.Sort(ports);

    comboPortName.Items.AddRange(ports);

    comboPortName.SelectedIndex = comboPortName.Items.Count > 0 ? 0 : -1;

    comboBaudrate.SelectedIndex = comboBaudrate.Items.IndexOf("19200");

    //初始化SerialPort对象

    comm.NewLine ="/r/n";

    comm.RtsEnable =true;//根据实际情况吧。

    //添加事件注册

    comm.DataReceived += comm_DataReceived;

    }

    voidcomm_DataReceived(objectsender, SerialDataReceivedEventArgs e)

    {

    if(Closing)return;//如果正在关闭,忽略操作,直接返回,尽快的完成串口监听线程的一次循环

    try

    {

    Listening =true;//设置标记,说明我已经开始处理数据,一会儿要使用系统UI的。

    intn = comm.BytesToRead;//先记录下来,避免某种原因,人为的原因,操作几次之间时间长,缓存不一致

    byte[] buf =newbyte[n];//声明一个临时数组存储当前来的串口数据

    received_count += n;//增加接收计数

    comm.Read(buf, 0, n);//读取缓冲数据

    /

    //

    booldata_1_catched =false;//缓存记录数据是否捕获到

    //1.缓存数据

    buffer.AddRange(buf);

    //2.完整性判断

    while(buffer.Count >= 4)//至少要包含头(2字节)+长度(1字节)+校验(1字节)

    {

    //请不要担心使用>=,因为>=已经和>,和=2个符号

    //2.1 查找数据头

    if(buffer[0] == 0xAA && buffer[1] == 0x44)

    {

    //2.2 探测缓存数据是否有一条数据的字节,如果不够,就不用费劲的做其他验证了

    //前面已经限定了剩余长度>=4,那我们这里一定能访问到buffer[2]这个长度

    intlen = buffer[2];//数据长度

    //数据完整判断第一步,长度是否足够

    //len是数据段长度,4个字节是while行注释的3部分长度

    if(buffer.Count 

    //这里确保数据长度足够,数据头标志找到,我们开始计算校验

    //2.3 校验数据,确认数据正确

    //异或校验,逐个字节异或得到校验码

    bytechecksum = 0;

    for(inti = 0; i 

    {

    checksum ^= buffer[i];

    }

    if(checksum != buffer[len + 3])//如果数据校验失败,丢弃这一包数据

    {

    buffer.RemoveRange(0, len + 4);//从缓存中删除错误数据

    continue;//继续下一次循环

    }

    //至此,已经被找到了一条完整数据。我们将数据直接分析,或是缓存起来一起分析

    //我们这里采用的办法是缓存一次,好处就是如果你某种原因,数据堆积在缓存buffer中

    //已经很多了,那你需要循环的找到最后一组,只分析最新数据,过往数据你已经处理不及时

    //了,就不要浪费更多时间了,这也是考虑到系统负载能够降低。

    buffer.CopyTo(0, binary_data_1, 0, len + 4);//复制一条完整数据到具体的数据缓存

    data_1_catched =true;

    buffer.RemoveRange(0, len + 4);//正确分析一条数据,从缓存中移除数据。

    }

    else

    {

    //这里是很重要的,如果数据开始不是头,则删除数据

    buffer.RemoveAt(0);

    }

    }

    //分析数据

    if(data_1_catched)

    {

    //我们的数据都是定好格式的,所以当我们找到分析出的数据1,就知道固定位置一定是这些数据,我们只要显示就可以了

    stringdata = binary_data_1[3].ToString("X2") +" "+ binary_data_1[4].ToString("X2") +" "+

    binary_data_1[5].ToString("X2") +" "+ binary_data_1[6].ToString("X2") +" "+

    binary_data_1[7].ToString("X2");

    //更新界面

    this.Invoke((EventHandler)(delegate{ txData.Text = data; }));

    }

    //如果需要别的协议,只要扩展这个data_n_catched就可以了。往往我们协议多的情况下,还会包含数据编号,给来的数据进行

    //编号,协议优化后就是: 头+编号+长度+数据+校验

    //协议解析>

    /

    builder.Clear();//清除字符串构造器的内容

    //因为要访问ui资源,所以需要使用invoke方式同步ui。

    this.Invoke((EventHandler)(delegate

    {

    //判断是否是显示为16禁止

    if(checkBoxHexView.Checked)

    {

    //依次的拼接出16进制字符串

    foreach(bytebinbuf)

    {

    builder.Append(b.ToString("X2") +" ");

    }

    }

    else

    {

    //直接按ASCII规则转换成字符串

    builder.Append(Encoding.ASCII.GetString(buf));

    }

    //追加的形式添加到文本框末端,并滚动到最后。

    this.txGet.AppendText(builder.ToString());

    //修改接收计数

    labelGetCount.Text ="Get:"+ received_count.ToString();

    }));

    }

    finally

    {

    Listening =false;//我用完了,ui可以关闭串口了。

    }

    }

    privatevoidbuttonOpenClose_Click(objectsender, EventArgs e)

    {

    //根据当前串口对象,来判断操作

    if(comm.IsOpen)

    {

    Closing =true;

    while(Listening) Application.DoEvents();

    //打开时点击,则关闭串口

    comm.Close();

    }

    else

    {

    //关闭时点击,则设置好端口,波特率后打开

    comm.PortName = comboPortName.Text;

    comm.BaudRate =int.Parse(comboBaudrate.Text);

    try

    {

    comm.Open();

    }

    catch(Exception ex)

    {

    //捕获到异常信息,创建一个新的comm对象,之前的不能用了。

    comm =newSerialPort();

    //现实异常信息给客户。

    MessageBox.Show(ex.Message);

    }

    }

    //设置按钮的状态

    buttonOpenClose.Text = comm.IsOpen ?"Close":"Open";

    buttonSend.Enabled = comm.IsOpen;

    }

    //动态的修改获取文本框是否支持自动换行。

    privatevoidcheckBoxNewlineGet_CheckedChanged(objectsender, EventArgs e)

    {

    txGet.WordWrap = checkBoxNewlineGet.Checked;

    }

    privatevoidbuttonSend_Click(objectsender, EventArgs e)

    {

    //定义一个变量,记录发送了几个字节

    intn = 0;

    //16进制发送

    if(checkBoxHexSend.Checked)

    {

    //我们不管规则了。如果写错了一些,我们允许的,只用正则得到有效的十六进制数

    MatchCollection mc = Regex.Matches(txSend.Text, @"(?i)[/da-f]{2}");

    List buf =newList();//填充到这个临时列表中

    //依次添加到列表中

    foreach(Match minmc)

    {

    buf.Add(byte.Parse(m.Value, System.Globalization.NumberStyles.HexNumber));

    }

    //转换列表为数组后发送

    comm.Write(buf.ToArray(), 0, buf.Count);

    //记录发送的字节数

    n = buf.Count;

    }

    else//ascii编码直接发送

    {

    //包含换行符

    if(checkBoxNewlineSend.Checked)

    {

    comm.WriteLine(txSend.Text);

    n = txSend.Text.Length + 2;

    }

    else//不包含换行符

    {

    comm.Write(txSend.Text);

    n = txSend.Text.Length;

    }

    }

    send_count += n;//累加发送字节数

    labelSendCount.Text ="Send:"+ send_count.ToString();//更新界面

    }

    privatevoidbuttonReset_Click(objectsender, EventArgs e)

    {

    //复位接受和发送的字节数计数器并更新界面。

    send_count = received_count = 0;

    labelGetCount.Text ="Get:0";

    labelSendCount.Text ="Send:0";

    }

    }

    }

    using System;

    using System.Collections.Generic;

    using System.ComponentModel;

    using System.Data;

    using System.Drawing;

    using System.Linq;

    using System.Text;

    using System.Windows.Forms;

    using System.IO.Ports;

    using System.Text.RegularExpressions;

    namespace SerialportSample

    {

    public partial class SerialportSampleForm : Form

    {

    private SerialPort comm = new SerialPort();

    private StringBuilder builder = new StringBuilder();//避免在事件处理方法中反复的创建,定义到外面。

    private long received_count = 0;//接收计数

    private long send_count = 0;//发送计数

    private bool Listening = false;//是否没有执行完invoke相关操作

    private bool Closing = false;//是否正在关闭串口,执行Application.DoEvents,并阻止再次invoke

    private List buffer = new List(4096);//默认分配1页内存,并始终限制不允许超过

    private byte[] binary_data_1 = new byte[9];//AA 44 05 01 02 03 04 05 EA

    public SerialportSampleForm()

    {

    InitializeComponent();

    }

    //窗体初始化

    private void Form1_Load(object sender, EventArgs e)

    {

    //初始化下拉串口名称列表框

    string[] ports = SerialPort.GetPortNames();

    Array.Sort(ports);

    comboPortName.Items.AddRange(ports);

    comboPortName.SelectedIndex = comboPortName.Items.Count > 0 ? 0 : -1;

    comboBaudrate.SelectedIndex = comboBaudrate.Items.IndexOf("19200");

    //初始化SerialPort对象

    comm.NewLine = "/r/n";

    comm.RtsEnable = true;//根据实际情况吧。

    //添加事件注册

    comm.DataReceived += comm_DataReceived;

    }

    void comm_DataReceived(object sender, SerialDataReceivedEventArgs e)

    {

    if (Closing) return;//如果正在关闭,忽略操作,直接返回,尽快的完成串口监听线程的一次循环

    try

    {

    Listening = true;//设置标记,说明我已经开始处理数据,一会儿要使用系统UI的。

    int n = comm.BytesToRead;//先记录下来,避免某种原因,人为的原因,操作几次之间时间长,缓存不一致

    byte[] buf = new byte[n];//声明一个临时数组存储当前来的串口数据

    received_count += n;//增加接收计数

    comm.Read(buf, 0, n);//读取缓冲数据

    /

    //

    bool data_1_catched = false;//缓存记录数据是否捕获到

    //1.缓存数据

    buffer.AddRange(buf);

    //2.完整性判断

    while (buffer.Count >= 4)//至少要包含头(2字节)+长度(1字节)+校验(1字节)

    {

    //请不要担心使用>=,因为>=已经和>,和=2个符号

    //2.1 查找数据头

    if (buffer[0] == 0xAA && buffer[1] == 0x44)

    {

    //2.2 探测缓存数据是否有一条数据的字节,如果不够,就不用费劲的做其他验证了

    //前面已经限定了剩余长度>=4,那我们这里一定能访问到buffer[2]这个长度

    int len = buffer[2];//数据长度

    //数据完整判断第一步,长度是否足够

    //len是数据段长度,4个字节是while行注释的3部分长度

    if (buffer.Count < len + 4) break;//数据不够的时候什么都不做

    //这里确保数据长度足够,数据头标志找到,我们开始计算校验

    //2.3 校验数据,确认数据正确

    //异或校验,逐个字节异或得到校验码

    byte checksum = 0;

    for (int i = 0; i < len + 3; i++)//len+3表示校验之前的位置

    {

    checksum ^= buffer[i];

    }

    if (checksum != buffer[len + 3]) //如果数据校验失败,丢弃这一包数据

    {

    buffer.RemoveRange(0, len + 4);//从缓存中删除错误数据

    continue;//继续下一次循环

    }

    //至此,已经被找到了一条完整数据。我们将数据直接分析,或是缓存起来一起分析

    //我们这里采用的办法是缓存一次,好处就是如果你某种原因,数据堆积在缓存buffer中

    //已经很多了,那你需要循环的找到最后一组,只分析最新数据,过往数据你已经处理不及时

    //了,就不要浪费更多时间了,这也是考虑到系统负载能够降低。

    buffer.CopyTo(0, binary_data_1, 0, len + 4);//复制一条完整数据到具体的数据缓存

    data_1_catched = true;

    buffer.RemoveRange(0, len + 4);//正确分析一条数据,从缓存中移除数据。

    }

    else

    {

    //这里是很重要的,如果数据开始不是头,则删除数据

    buffer.RemoveAt(0);

    }

    }

    //分析数据

    if (data_1_catched)

    {

    //我们的数据都是定好格式的,所以当我们找到分析出的数据1,就知道固定位置一定是这些数据,我们只要显示就可以了

    string data = binary_data_1[3].ToString("X2") + " " + binary_data_1[4].ToString("X2") + " " +

    binary_data_1[5].ToString("X2") + " " + binary_data_1[6].ToString("X2") + " " +

    binary_data_1[7].ToString("X2");

    //更新界面

    this.Invoke((EventHandler)(delegate { txData.Text = data; }));

    }

    //如果需要别的协议,只要扩展这个data_n_catched就可以了。往往我们协议多的情况下,还会包含数据编号,给来的数据进行

    //编号,协议优化后就是: 头+编号+长度+数据+校验

    //协议解析>

    /

    builder.Clear();//清除字符串构造器的内容

    //因为要访问ui资源,所以需要使用invoke方式同步ui。

    this.Invoke((EventHandler)(delegate

    {

    //判断是否是显示为16禁止

    if (checkBoxHexView.Checked)

    {

    //依次的拼接出16进制字符串

    foreach (byte b in buf)

    {

    builder.Append(b.ToString("X2") + " ");

    }

    }

    else

    {

    //直接按ASCII规则转换成字符串

    builder.Append(Encoding.ASCII.GetString(buf));

    }

    //追加的形式添加到文本框末端,并滚动到最后。

    this.txGet.AppendText(builder.ToString());

    //修改接收计数

    labelGetCount.Text = "Get:" + received_count.ToString();

    }));

    }

    finally

    {

    Listening = false;//我用完了,ui可以关闭串口了。

    }

    }

    private void buttonOpenClose_Click(object sender, EventArgs e)

    {

    //根据当前串口对象,来判断操作

    if (comm.IsOpen)

    {

    Closing = true;

    while (Listening) Application.DoEvents();

    //打开时点击,则关闭串口

    comm.Close();

    }

    else

    {

    //关闭时点击,则设置好端口,波特率后打开

    comm.PortName = comboPortName.Text;

    comm.BaudRate = int.Parse(comboBaudrate.Text);

    try

    {

    comm.Open();

    }

    catch(Exception ex)

    {

    //捕获到异常信息,创建一个新的comm对象,之前的不能用了。

    comm = new SerialPort();

    //现实异常信息给客户。

    MessageBox.Show(ex.Message);

    }

    }

    //设置按钮的状态

    buttonOpenClose.Text = comm.IsOpen ? "Close" : "Open";

    buttonSend.Enabled = comm.IsOpen;

    }

    //动态的修改获取文本框是否支持自动换行。

    private void checkBoxNewlineGet_CheckedChanged(object sender, EventArgs e)

    {

    txGet.WordWrap = checkBoxNewlineGet.Checked;

    }

    private void buttonSend_Click(object sender, EventArgs e)

    {

    //定义一个变量,记录发送了几个字节

    int n = 0;

    //16进制发送

    if (checkBoxHexSend.Checked)

    {

    //我们不管规则了。如果写错了一些,我们允许的,只用正则得到有效的十六进制数

    MatchCollection mc = Regex.Matches(txSend.Text, @"(?i)[/da-f]{2}");

    List buf = new List();//填充到这个临时列表中

    //依次添加到列表中

    foreach (Match m in mc)

    {

    buf.Add(byte.Parse(m.Value, System.Globalization.NumberStyles.HexNumber));

    }

    //转换列表为数组后发送

    comm.Write(buf.ToArray(), 0, buf.Count);

    //记录发送的字节数

    n = buf.Count;

    }

    else//ascii编码直接发送

    {

    //包含换行符

    if (checkBoxNewlineSend.Checked)

    {

    comm.WriteLine(txSend.Text);

    n = txSend.Text.Length + 2;

    }

    else//不包含换行符

    {

    comm.Write(txSend.Text);

    n = txSend.Text.Length;

    }

    }

    send_count += n;//累加发送字节数

    labelSendCount.Text = "Send:" + send_count.ToString();//更新界面

    }

    private void buttonReset_Click(object sender, EventArgs e)

    {

    //复位接受和发送的字节数计数器并更新界面。

    send_count = received_count = 0;

    labelGetCount.Text = "Get:0";

    labelSendCount.Text = "Send:0";

    }

    }

    }

    至此,你只要按这个协议格式发送数据到软件打开的串口。就能在数据的data标签显示出你的数据内容,我们现在是直接显示为:

    01 02 03 04 05

    也就是数据段内容。

    运行截图:

    发送模拟数据的界面,使用通用工具SSCOMM32.exe

    我们在回顾一下,一般二进制格式数据就是这样分析,分析数据长度是否足够,找到数据头,数据长度,校验,然后分析。

    分析方式很多。结合各自实际情况操作,可以使用序列化方式,但是wince不支持,也可以用BitConvert方式将连续的字节读取为某个类型的变量。

    希望看到这里,能给你带来帮助,欢迎大家和我讨论,希望经验丰富的朋友不吝赐教。上一篇中,有朋友说用BeginInvoke可以避免死锁问题,我暂时没有线,没有测试成功,改天测试后再公布结果。

    DataReceived事件中,最高效的做法是指缓存数据,然后异步的去分析数据。但是,这样较复杂,在效率要求不是很高的情况下(大多数情况),可以在DataReceived事件中缓存数据后,立刻进行数据完整性检查,有效性检查,分析数据,以及更新界面。但这么做有一个隐患,底层串口操作的效率依赖于数据分析和界面更新,任何一个环节频繁耗时过长,都会造成数据的堆积。文章只假设都不拖时间的情况。

    谢谢观赏,通讯协议分析系列,未完待续……

    展开全文
    weixin_39522423 2020-12-20 02:21:35
  • 1. 代码中串口监听和发送采用线程模式和异步模式; 2. 源代码在接收字符串时采用消息模式。 在实际应用中,发现采用消息模式,有可能会接收不到字符,最好采用线程模式。 启动另外一个线程专门用来处理接收的...

    使用SCOMMV23_龚建伟版串口代码监听串口注意事项:

    1. 代码中串口监听和发送采用线程模式和异步模式;

    2. 源代码在接收字符串时采用消息模式。


    在实际应用中,发现采用消息模式,有可能会接收不到字符,最好采用线程模式。

    启动另外一个线程专门用来处理接收的数据。

    在串口监听中采用先把接收到的数据放到缓冲区中,然后再激发一个事件,使处理接收数据的线程立即跑起来。

    展开全文
    jiangdf 2015-07-10 10:55:05
  • // 设置串口监听 serialPort.addEventListener(this); // 设置串口数据时间有效(可监听) serialPort.notifyOnDataAvailable(true); // 设置串口通讯参数:波特率,数据位,停止位,校验方式 serialPort....

    文章参考  飞机票

    下载Rxtx.jar

    下载地址位   http://files.cnblogs.com/files/Dreamer-1/mfz-rxtx-2.2-20081207-win-x64.zip (64位)

    eclipse里面导入RXTXcomm.jar 

    <dependency>
    		  <groupId>com.ruoyi</groupId>
    		  <artifactId>RXTXcomm</artifactId>
    		  <version>0.0.1-SNAPSHOT</version>
    </dependency>

    然后右键项目-->import

    为防止运行过程中抛出 java.lang.UnsatisfiedLinkError 错误或 gnu.io 下的类找不到,请将rxtx解压包中的 rxtxParallel.dll,rxtxSerial.dll 这两个文件复制到 C:\Windows\System32 目录下即可解决该错误

    新增一个项目启动是一起启动的方法

    import org.springframework.boot.ApplicationArguments;
    import org.springframework.boot.ApplicationRunner;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    import com.ruoyi.web.core.config.ParamConfig;
    import com.ruoyi.web.core.config.SerialPortUtils;
    
    
    
    /**
     * 继承Application接口后项目启动时会按照执行顺序执行run方法
     * 通过设置Order的value来指定执行的顺序
     */
    @Component
    @Order(value = 10)
    public class StartService implements ApplicationRunner {
    	
        @Override
        public void run(ApplicationArguments args) throws Exception {
            SerialPortUtils spu = new SerialPortUtils();
            ParamConfig paramConfig = new ParamConfig("COM1", 9600, 0, 8, 1);
            //初始化串口
            spu.init(paramConfig);
        }
    
    
    }

    SerialPortUtils里面实现对串口的操作,里面有一些自定义异常

    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.util.Enumeration;
    import java.util.TooManyListenersException;
    
    import org.springframework.stereotype.Component;
    
    import com.ruoyi.common.exception.serialport.CustomException;
    import com.ruoyi.common.exception.serialport.NoSuchPort;
    import com.ruoyi.common.exception.serialport.NotASerialPort;
    import com.ruoyi.common.exception.serialport.PortInUse;
    import com.ruoyi.common.exception.serialport.ReadDataFromSerialPortFailure;
    import com.ruoyi.common.exception.serialport.SendDataToSerialPortFailure;
    import com.ruoyi.common.exception.serialport.SerialPortInputStreamCloseFailure;
    import com.ruoyi.common.exception.serialport.SerialPortOutputStreamCloseFailure;
    import com.ruoyi.common.exception.serialport.TooManyListeners;
    
    import gnu.io.CommPortIdentifier;
    import gnu.io.PortInUseException;
    import gnu.io.SerialPort;
    import gnu.io.SerialPortEvent;
    import gnu.io.SerialPortEventListener;
    import gnu.io.UnsupportedCommOperationException;
    
    /**
     * 串口参数的配置 串口一般有如下参数可以在该串口打开以前进行配置: 包括串口号,波特率,输入/输出流控制,数据位数,停止位和奇偶校验。
     */
    // 注:串口操作类一定要继承SerialPortEventListener
    @Component
    public class SerialPortUtils implements SerialPortEventListener {
        // 检测系统中可用的通讯端口类
        private CommPortIdentifier commPortId;
        // 枚举类型
        private Enumeration<CommPortIdentifier> portList;
        // RS232串口
        private SerialPort serialPort;
        // 输入流
        private InputStream inputStream;
        // 输出流
        private OutputStream outputStream;
        // 保存串口返回信息
        private String data;
        // 保存串口返回信息十六进制
        private String dataHex;
        /**
         * 初始化串口
         * @author LinWenLi
         * @date 2018年7月21日下午3:44:16
         * @Description: TODO
         * @param: paramConfig  存放串口连接必要参数的对象(会在下方给出类代码)    
         * @return: void      
         * @throws
         */
        @SuppressWarnings("unchecked")
        public void init(ParamConfig paramConfig) {
            // 获取系统中所有的通讯端口
            portList = CommPortIdentifier.getPortIdentifiers();
            // 记录是否含有指定串口
            boolean isExsist = false;
            // 循环通讯端口
            while (portList.hasMoreElements()) {
                commPortId = portList.nextElement();
                // 判断是否是串口
                if (commPortId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
                    // 比较串口名称是否是指定串口
                    if (paramConfig.getSerialNumber().equals(commPortId.getName())) {
                        // 串口存在
                        isExsist = true;
                        // 打开串口
                        try {
                            // open:(应用程序名【随意命名】,阻塞时等待的毫秒数)
                            serialPort = (SerialPort) commPortId.open(Object.class.getSimpleName(), 2000);
                            // 设置串口监听
                            serialPort.addEventListener(this);
                            // 设置串口数据时间有效(可监听)
                            serialPort.notifyOnDataAvailable(true);
                            // 设置串口通讯参数:波特率,数据位,停止位,校验方式
                            serialPort.setSerialPortParams(paramConfig.getBaudRate(), paramConfig.getDataBit(),
                                    paramConfig.getStopBit(), paramConfig.getCheckoutBit());
                        } catch (PortInUseException e) {
                            new PortInUse().printStackTrace();;
                        } catch (TooManyListenersException e) {
                            new TooManyListeners().printStackTrace();;
                        } catch (UnsupportedCommOperationException e) {
                            new NotASerialPort().printStackTrace();;
                        }
                        // 结束循环
                        break;
                    }
                }
            }
            // 若不存在该串口则抛出异常
            if (!isExsist) {
                new NoSuchPort().printStackTrace();;
            }
        }
    
        /**
         * 实现接口SerialPortEventListener中的方法 读取从串口中接收的数据
         */
        @Override
        public void serialEvent(SerialPortEvent event) {
            switch (event.getEventType()) {
            case SerialPortEvent.BI: // 通讯中断
            case SerialPortEvent.OE: // 溢位错误
            case SerialPortEvent.FE: // 帧错误
            case SerialPortEvent.PE: // 奇偶校验错误
            case SerialPortEvent.CD: // 载波检测
            case SerialPortEvent.CTS: // 清除发送
            case SerialPortEvent.DSR: // 数据设备准备好
            case SerialPortEvent.RI: // 响铃侦测
            case SerialPortEvent.OUTPUT_BUFFER_EMPTY: // 输出缓冲区已清空
                break;
            case SerialPortEvent.DATA_AVAILABLE: // 有数据到达
                // 调用读取数据的方法
                readComm();
                break;
            default:
                break;
            }
        }
    
        /**
         * 读取串口返回信息
         * @author LinWenLi
         * @date 2018年7月21日下午3:43:04
         * @return: void      
         */
        public void readComm() {
            try {
                inputStream = serialPort.getInputStream();
                // 通过输入流对象的available方法获取数组字节长度
                byte[] readBuffer = new byte[inputStream.available()];
                // 从线路上读取数据流
                int len = 0;
                while ((len = inputStream.read(readBuffer)) != -1) {
                    //获取串口返回数据
                	data = new String(readBuffer, 0, len).trim();
                    //转为十六进制数据
                    dataHex = bytesToHexString(readBuffer);
                    System.out.println("data:" + data);
                    System.out.println("dataHex:" + dataHex);// 读取后置空流对象
                    inputStream.close();
                    inputStream = null;
                    break;
                }
            } catch (IOException e) {
            	new ReadDataFromSerialPortFailure().printStackTrace();  
            }
        }
    
        /**
         * 发送信息到串口
         * @author LinWenLi
         * @date 2018年7月21日下午3:45:22
         * @param: data      
         * @return: void      
         * @throws
         */
        public void sendComm(String data) {
            byte[] writerBuffer = null;
            try {
                writerBuffer = hexToByteArray(data);
            } catch (NumberFormatException e) {
                new CustomException().printStackTrace(); 
            }
            try {
                outputStream = serialPort.getOutputStream();
                outputStream.write(writerBuffer);
                outputStream.flush();
            } catch (NullPointerException e) {
                new NoSuchPort().printStackTrace();;
            } catch (IOException e) {
                new SendDataToSerialPortFailure().printStackTrace();;
            }
        }
    
        /**
         * 关闭串口
         * @author LinWenLi
         * @date 2018年7月21日下午3:45:43
         * @Description: 关闭串口
         * @param:       
         * @return: void      
         * @throws
         */
        public void closeSerialPort() {
            if (serialPort != null) {
                serialPort.notifyOnDataAvailable(false);
                serialPort.removeEventListener();
                if (inputStream != null) {
                    try {
                        inputStream.close();
                        inputStream = null;
                    } catch (IOException e) {
                        new SerialPortInputStreamCloseFailure().printStackTrace();
                    }
                }
                if (outputStream != null) {
                    try {
                        outputStream.close();
                        outputStream = null;
                    } catch (IOException e) {
                        new SerialPortOutputStreamCloseFailure().printStackTrace();
                    }
                }
                serialPort.close();
                serialPort = null;
            }
        }
    
        /**
         * 十六进制串口返回值获取
         */
        public String getDataHex() {
            String result = dataHex;
            // 置空执行结果
            dataHex = null;
            // 返回执行结果
            return result;
        }
    
        /**
         * 串口返回值获取
         */
        public String getData() {
            String result = data;
            // 置空执行结果
            data = null;
            // 返回执行结果
            return result;
        }
    
        /**
         * Hex字符串转byte
         * @param inHex 待转换的Hex字符串
         * @return 转换后的byte
         */
        public static byte hexToByte(String inHex) {
            return (byte) Integer.parseInt(inHex, 16);
        }
    
        /**
         * hex字符串转byte数组
         * @param inHex 待转换的Hex字符串
         * @return 转换后的byte数组结果
         */
        public static byte[] hexToByteArray(String inHex) {
            int hexlen = inHex.length();
            byte[] result;
            if (hexlen % 2 == 1) {
                // 奇数
                hexlen++;
                result = new byte[(hexlen / 2)];
                inHex = "0" + inHex;
            } else {
                // 偶数
                result = new byte[(hexlen / 2)];
            }
            int j = 0;
            for (int i = 0; i < hexlen; i += 2) {
                result[j] = hexToByte(inHex.substring(i, i + 2));
                j++;
            }
            return result;
        }
    
        /**
         * 数组转换成十六进制字符串
         * @param byte[]
         * @return HexString
         */
        public static final String bytesToHexString(byte[] bArray) {
            StringBuffer sb = new StringBuffer(bArray.length);
            String sTemp;
            for (int i = 0; i < bArray.length; i++) {
                sTemp = Integer.toHexString(0xFF & bArray[i]);
                if (sTemp.length() < 2)
                    sb.append(0);
                sb.append(sTemp.toUpperCase());
            }
            return sb.toString();
        }
    }

    ParamConfig是串口连接的实体类

    public class ParamConfig {
    
    	private String serialNumber;// 串口号
        private int baudRate;       // 波特率
        private int checkoutBit;    // 校验位
        private int dataBit;        // 数据位
        private int stopBit;        // 停止位
        
        public ParamConfig() {}
            
        /**
         * 构造方法
         * @param serialNumber    串口号
         * @param baudRate        波特率
         * @param checkoutBit    校验位
         * @param dataBit        数据位
         * @param stopBit        停止位
         */
        public ParamConfig(String serialNumber, int baudRate, int checkoutBit, int dataBit, int stopBit) {
            this.serialNumber = serialNumber;
            this.baudRate = baudRate;
            this.checkoutBit = checkoutBit;
            this.dataBit = dataBit;
            this.stopBit = stopBit;
        }
    
    	public String getSerialNumber() {
    		return serialNumber;
    	}
    
    	public void setSerialNumber(String serialNumber) {
    		this.serialNumber = serialNumber;
    	}
    
    	public int getBaudRate() {
    		return baudRate;
    	}
    
    	public void setBaudRate(int baudRate) {
    		this.baudRate = baudRate;
    	}
    
    	public int getCheckoutBit() {
    		return checkoutBit;
    	}
    
    	public void setCheckoutBit(int checkoutBit) {
    		this.checkoutBit = checkoutBit;
    	}
    
    	public int getDataBit() {
    		return dataBit;
    	}
    
    	public void setDataBit(int dataBit) {
    		this.dataBit = dataBit;
    	}
    
    	public int getStopBit() {
    		return stopBit;
    	}
    
    	public void setStopBit(int stopBit) {
    		this.stopBit = stopBit;
    	}
    }
    

     

    展开全文
    jiushancunMonkeyKing 2019-09-01 10:05:40
  • u013363811 2015-03-15 11:23:53
  • newbie_xymt 2018-05-25 18:52:41
  • 3星
    5.87MB yangshishu 2014-11-27 09:38:07
  • 3星
    2KB weixin_42666036 2021-09-28 19:51:07
  • u010534192 2016-11-20 12:25:11
  • weixin_35696075 2021-03-08 09:24:18
  • Lance_welcome 2021-09-07 16:04:34
  • u010988850 2014-11-19 11:07:33
  • 155KB loes012 2018-12-25 14:56:46
  • baidu_36583119 2020-07-11 16:39:37
  • culh2177 2020-07-31 21:51:23
  • 43.28MB cubeking 2020-08-10 09:51:52
  • V_AYA_V 2021-05-21 09:47:24
  • 3星
    235KB jsnthajw 2010-01-15 12:05:22
  • Jane0418 2015-03-13 06:47:55
  • cuankuangzhong6373 2019-06-09 09:12:04
  • weixin_45121946 2020-07-05 13:48:17
  • zhujunwddp 2020-11-12 16:09:15
  • wind8303 2009-01-12 22:03:00
  • GodSure0914 2020-07-18 14:28:04
  • adazone 2021-04-19 19:01:47
  • weixin_29497981 2021-05-20 03:41:53
  • 5星
    40.7MB weixin_39442506 2019-09-06 16:58:23
  • lengyuefeng212 2021-02-26 19:28:40
  • weixin_42544051 2019-11-08 13:44:08

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,109
精华内容 1,243
关键字:

串口监听指令

友情链接: 89C51.rar