基于WPF編寫(xiě)一個(gè)串口轉(zhuǎn)UDP工具
串口是設(shè)備和上位機(jī)通信的常用接口,UDP則是網(wǎng)絡(luò)通信常用的通信協(xié)議,通過(guò)將串口設(shè)備上傳的指令,用UDP發(fā)送出去,或者將UDP傳來(lái)的指令轉(zhuǎn)發(fā)給串口設(shè)備,就可以實(shí)現(xiàn)設(shè)備的遠(yuǎn)程控制。所以,串口和UDP之間的相互轉(zhuǎn)換是非常有意義的。
如果不熟悉C#串口以及UDP通信的相關(guān)內(nèi)容,可以參考這兩篇博客:C#串口通信 C# UDP通信
本項(xiàng)目會(huì)用到基于Task的并發(fā)編程,如果不了解,可以參照這篇:Task詳解
框架準(zhǔn)備
盡管希望做一個(gè)轉(zhuǎn)發(fā)工具,但如果自身不會(huì)發(fā)送的話,那還得再用其他軟件進(jìn)行測(cè)試,所以這個(gè)轉(zhuǎn)發(fā)工具,理應(yīng)具備串口和UDP協(xié)議的全部功能,這一點(diǎn)也要在界面上體現(xiàn)出來(lái)。
新建一個(gè)WPF項(xiàng)目,名字是portUDP,然后開(kāi)始布局,結(jié)果如下
其中,串口設(shè)置中包含波特率、數(shù)據(jù)位、停止位和奇偶校驗(yàn)等信息,由于不常更換,所以隱藏起來(lái)。
串口只需要一個(gè),但UDP通信需要設(shè)置本機(jī)和目標(biāo)的IP地址與端口。自動(dòng)轉(zhuǎn)發(fā)單選框選中后,會(huì)自動(dòng)將接收到的串口數(shù)據(jù)轉(zhuǎn)給UDP,并且UDP收到的數(shù)據(jù)也會(huì)轉(zhuǎn)給串口。
窗口尺寸為320 × 640 320\times640320×640,外部采用一個(gè)DockPanel,并對(duì)常用控件進(jìn)行基本的外觀設(shè)置
<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>
左側(cè)控制面板被寫(xiě)在一個(gè)StackPanel中,最上面是一個(gè)Expander,里面包含串口設(shè)置的相關(guān)信息
<Expander Header="串口設(shè)置"> <UniformGrid Columns="2" Visibility="Visible"> <TextBlock Text="波特率"/> <ComboBox x:Name="cbBaud"/> <TextBlock Text="數(shù)據(jù)位"/> <ComboBox x:Name="cbDataBit"/> <TextBlock Text="停止位"/> <ComboBox x:Name="cbStopBit"/> <TextBlock Text="校驗(yàn)位"/> <ComboBox x:Name="cbParity"/> </UniformGrid> </Expander>
然后是一個(gè)GroupBox,用于進(jìn)行基本設(shè)置,其中兩個(gè)按鈕需要在連接后更改內(nèi)容,所以設(shè)了個(gè)名字。
<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="自動(dòng)轉(zhuǎn)發(fā)" IsChecked="True"/> <Button x:Name="btnUDP" Content="創(chuàng)建服務(wù)"/> </UniformGrid>
最后是發(fā)送文本框與發(fā)送按鈕等,內(nèi)容如下
<TextBox x:Name="txtSend" TextWrapping="Wrap" Height="70"/> <UniformGrid Columns="3"> <CheckBox Content="Hex" IsChecked="False"/> <Button Content="串口發(fā)送"/> <Button Content="UDP發(fā)送"/> <CheckBox Content="時(shí)間"/> <Button Content="清空日志"/> <Button Content="保存日志"/> </UniformGrid>
左側(cè)控制界面布局完成后,是右側(cè)的接收區(qū)域,內(nèi)容如下
<GroupBox Header="日志信息"> <TextBox x:Name="txtInfo" Height="270"/> </GroupBox>
初始化
由于.Net6.0不內(nèi)置串口庫(kù),所以需要額外下載,點(diǎn)擊菜單欄工具->NuGet包管理器->管理解決方案的NuGet包,點(diǎn)擊瀏覽選項(xiàng)卡,搜索Ports,選擇System.IO.Ports,安裝。
在對(duì)用戶界面進(jìn)行最簡(jiǎn)單的布局后,可以在C#代碼中,對(duì)一些ComboBox做進(jìn)一步的設(shè)置。
public void initComContent() { // 串口號(hào) cbPorts.ItemsSource = SerialPort.GetPortNames(); cbPorts.SelectedIndex = 0; // 波特率 cbBaud.ItemsSource = new int[] { 9600, 19200, 38400, 115200 }; cbBaud.SelectedIndex = 3; // 數(shù)據(jù)位 cbDataBit.ItemsSource = Enumerable.Range(1, 8); cbDataBit.SelectedIndex = 7; // 校驗(yàn)位 cbParity.ItemsSource = Enum.GetNames(typeof(Parity)); cbParity.SelectedIndex = 0; //停止位 cbStopBit.ItemsSource = Enum.GetNames(typeof(StopBits)); cbStopBit.SelectedIndex = 1; }
這樣,在打開(kāi)軟件之后,串口設(shè)置如下
串口設(shè)置
接下來(lái)設(shè)置串口,在xml編輯界面,將btnPort后面添加Click="btnPort_Click"后,按下F12,IDE會(huì)自動(dòng)創(chuàng)建對(duì)應(yīng)的函數(shù)。
<Button x:Name="btnPort" Content="連接串口" Click="btnPort_Click"/>
在寫(xiě)串口開(kāi)關(guān)按鈕的控制指令之前,先新建一個(gè)全局的串口對(duì)象,然后寫(xiě)btnPort_Click內(nèi)容
SerialPort sp; private void btnPort_Click(object sender, RoutedEventArgs e) { if (btnPort.Content.ToString() == "打開(kāi)串口") { 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}打開(kāi)成功"); } catch(Exception ex) { txtInfo.AppendText($"串口{name}打開(kāi)失敗,原因是{ex}"); } } else { try { sp.Close(); initComContent(); } catch (Exception ex) { txtInfo.AppendText($"串口關(guān)閉失敗,原因是{ex}"); } } btnPort.Content = sp.IsOpen ? "關(guān)閉串口" : "打開(kāi)串口"; }
其中sp.DataReceived += Sp_DataReceived;新增一個(gè)委托,用于規(guī)范串口接收到數(shù)據(jù)后的行為。
UDP設(shè)置
和串口設(shè)置相同,UDP也需要新建用于UDP通信的全局變量,包括本機(jī)節(jié)點(diǎn)、目標(biāo)節(jié)點(diǎn)以及UDP服務(wù)。
UdpClient udp;
IPEndPoint ptSrc;
IPEndPoint ptDst;
然后設(shè)置創(chuàng)建服務(wù)按鈕后,其邏輯與串口是相似的,都是在創(chuàng)建或關(guān)閉服務(wù)時(shí),用try-catch語(yǔ)句以找到錯(cuò)誤。
private void btnUDP_Click(object sender, RoutedEventArgs e) { if (btnUDP.Content.ToString() == "創(chuàng)建服務(wù)") { 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("成功創(chuàng)建服務(wù)"); btnUDP.Content = "關(guān)閉服務(wù)"; }catch(Exception ex) { txtInfo.AppendText($"服務(wù)創(chuàng)建失敗,原因?yàn)閧ex}\n"); } } else { try { udp.Close(); btnUDP.Content = "創(chuàng)建服務(wù)"; } catch(Exception ex) { txtInfo.AppendText($"服務(wù)關(guān)閉失敗,原因?yàn)閧ex}"); } } ???????}
發(fā)送設(shè)置
首先是串口發(fā)送,在xaml文件中,為串口發(fā)送按鈕掛載一個(gè)Click動(dòng)作,其內(nèi)容即為串口發(fā)送功能
<Button Content="串口發(fā)送" Click="btnPortSend_Click"/>
private void btnPortSend_Click(object sender, RoutedEventArgs e) { var data = Encoding.UTF8.GetBytes(txtSend.Text); txtInfo.AppendText($"串口發(fā)送{txtSend.Text}\n"); sp.Write(data, 0, data.Length); }
然后是UDP發(fā)送,其改裝過(guò)程也大同小異
<Button Content="UDP發(fā)送" Click="btnUDPSend_Click"/>
private void btnUDPSend_Click(object sender, RoutedEventArgs e) { var data = Encoding.UTF8.GetBytes(txtSend.Text); txtInfo.AppendText($"UDP發(fā)送{txtSend.Text}\n"); udp.Send(data, data.Length, ptDst); //將內(nèi)容發(fā)給ptDst }
轉(zhuǎn)發(fā)設(shè)置
轉(zhuǎn)發(fā)是本軟件的核心功能,但其前提是接收到數(shù)據(jù)。所以,先來(lái)充實(shí)創(chuàng)建串口時(shí)就已經(jīng)提到的Sp_DataReceived函數(shù)
private void Sp_DataReceived(object sender, SerialDataReceivedEventArgs e) { byte[] data = new byte[sp.BytesToRead]; sp.Read(data, 0, data.Length);//從串口讀取數(shù)據(jù) Dispatcher.Invoke(() => spReceived(data)); } private void spReceived(byte[] data) { string info = Encoding.UTF8.GetString(data); txtInfo.AppendText("串口接收數(shù)據(jù):{info}"); if ((bool)chkTransmit.IsChecked) { try { udp.Send(data, data.Length, ptDst); //將內(nèi)容發(fā)給ptDst txtInfo.AppendText($"UDP轉(zhuǎn)發(fā):{info}"); } catch (Exception ex) { txtInfo.AppendText($"UDP轉(zhuǎn)發(fā)失敗,原因?yàn)閧ex}\nd"); } } }
然后創(chuàng)建UPD接收和轉(zhuǎn)發(fā)函數(shù)
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接收數(shù)據(jù):{info}"); if ((bool)chkTransmit.IsChecked) { try { sp.Write(data, 0, data.Length); txtInfo.AppendText($"串口轉(zhuǎn)發(fā){info}\n"); } catch (Exception ex) { txtInfo.AppendText($"串口轉(zhuǎn)發(fā)失敗,原因?yàn)閧ex}"); } } }
其中,udpReceiving里面是一個(gè)死循環(huán),表示一直等待UDP信息的到來(lái),這個(gè)函數(shù)作為一個(gè)Task的創(chuàng)建時(shí)機(jī),自然是在UDP服務(wù)創(chuàng)建之時(shí),
//... txtInfo.AppendText("成功創(chuàng)建服務(wù)"); //這是一個(gè)全局變量 cts = new CancellationTokenSource(); Task.Run(() => udpReceiving(cts.Token), cts.Token);
測(cè)試
至此,一個(gè)串口-UDP轉(zhuǎn)發(fā)工具便算完成了,盡管界面上還有幾個(gè)功能沒(méi)有實(shí)現(xiàn),比如Hex以及時(shí)間的單選框等,但這些均為錦上添花。
下面做一下基礎(chǔ)的測(cè)試,效果如下
到此這篇關(guān)于基于WPF編寫(xiě)一個(gè)串口轉(zhuǎn)UDP工具的文章就介紹到這了,更多相關(guān)WPF串口轉(zhuǎn)UDP內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#使用委托(delegate)實(shí)現(xiàn)在兩個(gè)form之間傳遞數(shù)據(jù)的方法
這篇文章主要介紹了C#使用委托(delegate)實(shí)現(xiàn)在兩個(gè)form之間傳遞數(shù)據(jù)的方法,涉及C#委托的使用技巧,需要的朋友可以參考下2015-04-04C#固定大小緩沖區(qū)及使用指針復(fù)制數(shù)據(jù)詳解
這篇文章主要為大家介紹了C#固定大小緩沖區(qū)及使用指針復(fù)制數(shù)據(jù)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12C#和vb.net實(shí)現(xiàn)PDF 添加可視化和不可見(jiàn)數(shù)字簽名
本文通過(guò)C#程序代碼展示如何給PDF文檔添加可視化數(shù)字簽名和不可見(jiàn)數(shù)字簽名。文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08Unity實(shí)現(xiàn)弧形移動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了Unity實(shí)現(xiàn)弧形移動(dòng)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06C# 獲取數(shù)據(jù)庫(kù)中所有表名、列名的示例代碼
這篇文章主要介紹了C# 獲取數(shù)據(jù)庫(kù)中所有表名、列名,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06C#模擬鏈表數(shù)據(jù)結(jié)構(gòu)的實(shí)例解析
這篇文章主要介紹了C#模擬鏈表數(shù)據(jù)結(jié)構(gòu)的實(shí)例解析,包括隊(duì)雙向鏈表的模擬方法,例子中隊(duì)鏈表的操作也有很好的說(shuō)明,需要的朋友可以參考下2016-04-04