C#实战:基于WPF开发一个串口转UDP工具

news/2024/11/17 21:44:45/

文章目录

      • 框架准备
      • 初始化
      • 串口设置
      • UDP设置
      • 发送设置
      • 转发设置
      • 测试

串口是设备和上位机通信的常用接口,UDP则是网络通信常用的通信协议,通过将串口设备上传的指令,用UDP发送出去,或者将UDP传来的指令转发给串口设备,就可以实现设备的远程控制。所以,串口和UDP之间的相互转换是非常有意义的。

如果不熟悉C#串口以及UDP通信的相关内容,可以参考这两篇博客:C#串口通信💎C# UDP通信

本项目会用到基于Task的并发编程,如果不了解,可以参照这篇:Task详解

有关C#的其他基础知识,可参考这个C#专栏目录

框架准备

尽管希望做一个转发工具,但如果自身不会发送的话,那还得再用其他软件进行测试,所以这个转发工具,理应具备串口和UDP协议的全部功能,这一点也要在界面上体现出来。

新建一个WPF项目,名字是portUDP,然后开始布局,结果如下

在这里插入图片描述

其中,串口设置中包含波特率、数据位、停止位和奇偶校验等信息,由于不常更换,所以隐藏起来。

串口只需要一个,但UDP通信需要设置本机和目标的IP地址与端口。自动转发单选框选中后,会自动将接收到的串口数据转给UDP,并且UDP收到的数据也会转给串口。

窗口尺寸为320×640320\times640320×640,外部采用一个DockPanel,并对常用控件进行基本的外观设置

<DockPanel.Resources><Style TargetType="TextBox"><Setter Property="VerticalAlignment" Value="Center"/><Setter Property="Margin" Value="2"/></Style><Style TargetType="TextBlock"><Setter Property="VerticalAlignment" Value="Center"/><Setter Property="HorizontalAlignment" Value="Center"/></Style><Style TargetType="ComboBox"><Setter Property="VerticalAlignment" Value="Center"/><Setter Property="Margin" Value="2"/></Style><Style TargetType="Button"><Setter Property="VerticalAlignment" Value="Center"/><Setter Property="Margin" Value="2"/></Style><Style TargetType="CheckBox"><Setter Property="VerticalAlignment" Value="Center"/><Setter Property="HorizontalAlignment" Value="Center"/><Setter Property="Margin" Value="2"/></Style>
</DockPanel.Resources>

左侧控制面板被写在一个StackPanel中,最上面是一个Expander,里面包含串口设置的相关信息

<Expander Header="串口设置"><UniformGrid Columns="2" Visibility="Visible"><TextBlock Text="波特率"/><ComboBox x:Name="cbBaud"/><TextBlock Text="数据位"/><ComboBox x:Name="cbDataBit"/><TextBlock Text="停止位"/><ComboBox x:Name="cbStopBit"/><TextBlock Text="校验位"/><ComboBox x:Name="cbParity"/></UniformGrid></Expander>

然后是一个GroupBox,用于进行基本设置,其中两个按钮需要在连接后更改内容,所以设了个名字。

<UniformGrid Columns="2"><ComboBox x:Name="cbPorts" Margin="2"/><Button x:Name="btnPort" Content="连接串口"/><TextBox x:Name="txtSrcIP" Text="127.0.0.1"/><TextBox x:Name="txtSrcPort" Text="91"/><TextBox x:Name="txtDstIP" Text="127.0.0.1"/><TextBox x:Name="txtDstPort" Text="91"/><CheckBox Content="自动转发" IsChecked="True"/><Button  x:Name="btnUDP" Content="创建服务"/>
</UniformGrid>

最后是发送文本框与发送按钮等,内容如下

<TextBox x:Name="txtSend" TextWrapping="Wrap" Height="70"/>
<UniformGrid Columns="3"><CheckBox Content="Hex" IsChecked="False"/><Button Content="串口发送"/><Button Content="UDP发送"/><CheckBox Content="时间"/><Button Content="清空日志"/><Button Content="保存日志"/>
</UniformGrid>

左侧控制界面布局完成后,是右侧的接收区域,内容如下

<GroupBox Header="日志信息"><TextBox x:Name="txtInfo"  Height="270"/>
</GroupBox>

初始化

由于.Net6.0不内置串口库,所以需要额外下载,点击菜单栏工具->NuGet包管理器->管理解决方案的NuGet包,点击浏览选项卡,搜索Ports,选择System.IO.Ports,安装。

在对用户界面进行最简单的布局后,可以在C#代码中,对一些ComboBox做进一步的设置。

public void initComContent()
{// 串口号cbPorts.ItemsSource = SerialPort.GetPortNames();cbPorts.SelectedIndex = 0;// 波特率cbBaud.ItemsSource = new int[] { 9600, 19200, 38400, 115200 };cbBaud.SelectedIndex = 3;// 数据位cbDataBit.ItemsSource = Enumerable.Range(1, 8);cbDataBit.SelectedIndex = 7;// 校验位cbParity.ItemsSource = Enum.GetNames(typeof(Parity));cbParity.SelectedIndex = 0;//停止位cbStopBit.ItemsSource = Enum.GetNames(typeof(StopBits));cbStopBit.SelectedIndex = 1;
}

这样,在打开软件之后,串口设置如下

在这里插入图片描述

串口设置

接下来设置串口,在xml编辑界面,将btnPort后面添加Click="btnPort_Click"后,按下F12,IDE会自动创建对应的函数。

<Button x:Name="btnPort" Content="连接串口" Click="btnPort_Click"/>

在写串口开关按钮的控制指令之前,先新建一个全局的串口对象,然后写btnPort_Click内容

SerialPort sp;
private void btnPort_Click(object sender, RoutedEventArgs e)
{if (btnPort.Content.ToString() == "打开串口"){string name = cbPorts.SelectedItem.ToString();try{sp = new SerialPort(name,(int)cbBaud.SelectedItem,(Parity)cbParity.SelectedIndex,(int)cbDataBit.SelectedItem,(StopBits)cbStopBit.SelectedIndex);sp.Open();sp.DataReceived += Sp_DataReceived;txtInfo.AppendText($"串口{name}打开成功");}catch(Exception ex){txtInfo.AppendText($"串口{name}打开失败,原因是{ex}");}}else{try{sp.Close();initComContent();}catch (Exception ex){txtInfo.AppendText($"串口关闭失败,原因是{ex}");}}btnPort.Content = sp.IsOpen ? "关闭串口" : "打开串口";
}

其中sp.DataReceived += Sp_DataReceived;新增一个委托,用于规范串口接收到数据后的行为。

UDP设置

和串口设置相同,UDP也需要新建用于UDP通信的全局变量,包括本机节点、目标节点以及UDP服务。

UdpClient udp;
IPEndPoint ptSrc;
IPEndPoint ptDst;

然后设置创建服务按钮后,其逻辑与串口是相似的,都是在创建或关闭服务时,用try-catch语句以找到错误。

private void btnUDP_Click(object sender, RoutedEventArgs e)
{if (btnUDP.Content.ToString() == "创建服务"){try{ptSrc = new IPEndPoint(IPAddress.Parse(txtSrcIP.Text), int.Parse(txtSrcPort.Text));ptDst = new IPEndPoint(IPAddress.Parse(txtDstIP.Text), int.Parse(txtDstPort.Text));udp = new UdpClient(ptSrc);txtInfo.AppendText("成功创建服务");btnUDP.Content = "关闭服务";}catch(Exception ex){txtInfo.AppendText($"服务创建失败,原因为{ex}\n");}}else{try{udp.Close();btnUDP.Content = "创建服务";}catch(Exception ex){txtInfo.AppendText($"服务关闭失败,原因为{ex}");}}}

发送设置

首先是串口发送,在xaml文件中,为串口发送按钮挂载一个Click动作,其内容即为串口发送功能

<Button Content="串口发送" Click="btnPortSend_Click"/>
private void btnPortSend_Click(object sender, RoutedEventArgs e)
{var data = Encoding.UTF8.GetBytes(txtSend.Text);txtInfo.AppendText($"串口发送{txtSend.Text}\n");sp.Write(data, 0, data.Length);
}

然后是UDP发送,其改装过程也大同小异

<Button Content="UDP发送" Click="btnUDPSend_Click"/>
private void btnUDPSend_Click(object sender, RoutedEventArgs e)
{var data = Encoding.UTF8.GetBytes(txtSend.Text);txtInfo.AppendText($"UDP发送{txtSend.Text}\n");udp.Send(data, data.Length, ptDst);    //将内容发给ptDst
}

转发设置

转发是本软件的核心功能,但其前提是接收到数据。所以,先来充实创建串口时就已经提到的Sp_DataReceived函数

private void Sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
{byte[] data = new byte[sp.BytesToRead];sp.Read(data, 0, data.Length);//从串口读取数据Dispatcher.Invoke(() => spReceived(data));
}private void spReceived(byte[] data)
{string info = Encoding.UTF8.GetString(data);txtInfo.AppendText("串口接收数据:{info}");if ((bool)chkTransmit.IsChecked){try{udp.Send(data, data.Length, ptDst);    //将内容发给ptDsttxtInfo.AppendText($"UDP转发:{info}");}catch (Exception ex){txtInfo.AppendText($"UDP转发失败,原因为{ex}\nd");}}
}

然后创建UPD接收和转发函数

private void udpReceiving(CancellationToken token)
{while (! token.IsCancellationRequested){var data = udp.Receive(ref ptDst);Dispatcher.Invoke(() => udpReceived(data));}
}
private void udpReceived(byte[] data)
{string info = Encoding.UTF8.GetString(data);txtInfo.AppendText("UDP接收数据:{info}");if ((bool)chkTransmit.IsChecked){try{sp.Write(data, 0, data.Length);txtInfo.AppendText($"串口转发{info}\n");}catch (Exception ex){txtInfo.AppendText($"串口转发失败,原因为{ex}");}}
}

其中,udpReceiving里面是一个死循环,表示一直等待UDP信息的到来,这个函数作为一个Task的创建时机,自然是在UDP服务创建之时,

//...
txtInfo.AppendText("成功创建服务");
//这是一个全局变量
cts = new CancellationTokenSource();
Task.Run(() => udpReceiving(cts.Token), cts.Token);

测试

至此,一个串口-UDP转发工具便算完成了,尽管界面上还有几个功能没有实现,比如Hex以及时间的单选框等,但这些均为锦上添花。

下面做一下基础的测试,效果如下

在这里插入图片描述


http://www.ppmy.cn/news/38525.html

相关文章

1.12 从0开始学习Unity游戏开发--渲染概念

在我们继续扩大我们控制场景物体的逻辑之前&#xff0c;我希望先讲讲Unity中关于渲染的一些概念&#xff0c;如果不是做图形程序或者技术美术&#xff0c;可能做很久游戏开发也不一定用得上&#xff0c;但是既然要系统性的从0学起&#xff0c;那至少还是要了解一些最基本的内容…

Unity 命名行方式启动exe并携带参数信息

准备 Environment.GetCommandLineArgs 方法 作用&#xff1a;获取以命令行方式启动软件时输入的参数 返回值&#xff1a;字符串的第一个元素是可执行文件的路径 其他元素是自定义参数批处理Start命令 Start 文件名.exe 参数1 参数2 … 脚本 using System; using UnityEngine…

Android:使用LayerDrawable动态生成四宫格头像(包含双人、三人头像)

其实用自定义View也可以实现&#xff0c;我比较懒&#xff0c;就用LayerDrawable来创建一个新的Drawable资源实现。 举例4宫格&#xff0c;9宫格原理类似&#xff0c;每个图标的位置需要用边距慢慢调成预期的效果 效果如下&#xff1a; 双人头像&#xff1a; 三人头像&#x…

大数据平台开发——使用Java和Python调用Shell脚本

大数据平台开发——使用Java和Python调用Shell脚本 背景 在大数据平台开发中&#xff0c;经常会遇到需要调用Shell脚本的场景&#xff0c;倒不是说只能用Shell&#xff0c;毕竟大数据开发到头来一定是个语言无关的事情&#xff1a; 从Hive源码解读大数据开发为什么可以脱离S…

2022 第十三届蓝桥杯大赛软件赛省赛(第二场),C/C++ 大学B组题解

2022 第十三届蓝桥杯大赛软件赛省赛&#xff08;第二场&#xff09;&#xff0c;C/C 大学B组题解 文章目录第1题 —— 练习 &#xff08;5分&#xff09;第2题 —— 三角回文数 &#xff08;5分&#xff09;第3题 —— 卡片 &#xff08;10分&#xff09;第4题 —— 考勤刷卡 &…

关于文件同步中单向同步和双向同步

文件同步是确保两个或多个位置包含相同的最新文件的过程。如果您从一个位置添加&#xff0c;更改或删除文件&#xff0c;则同步过程将在另一位置添加&#xff0c;更改或删除相应的文件。同步可以是“ 双向 ”或“ 单向 ”。双向同步&#xff08;又名双向同步或双向同步&#xf…

左孩子右兄弟 蓝桥杯1451 python

题目描述 对于一棵多叉树&#xff0c;我们可以通过 “左孩子右兄弟” 表示法&#xff0c;将其转化成一棵二叉树。 如果我们认为每个结点的子结点是无序的&#xff0c;那么得到的二叉树可能不唯一。 换句话说&#xff0c;每个结点可以选任意子结点作为左孩子&#xff0c;并按…

进程与线程(一)

进程的概念、组成、特征 程序&#xff1a;是静态的&#xff0c;就是个存放在磁盘里的可执行文件&#xff0c;如&#xff1a;xx.exe 进程&#xff1a;进程是进程实体的运行过程&#xff0c;是系统进行资源分配和调度的一个独立单位。 同一个程序多次执行会对应多个进程。 进…