项目要求:下位机使用单片机,不断发送一个随机数值给上位机,上位机收到数据,显示在显示框中。
实验环境:vs2013
参考文档:https://docs.microsoft.com/zh-cn/dotnet/api/system.io.ports?view=netframework-4.7.2
下位机部分:https://blog.csdn.net/weixin_42462552/article/details/85561632
1.首先我们参照网上的串口调试助手将UI界面的大体框架完成。
2.其中数据接收区域和发送区域的文本需要实现自动换行功能,根据网上资料,需要设置TextWrapping的属性为"Wrap"。
3.接下来是对串口号、波特率、校验位等的一些内容设置,其中较复杂的是串口号的设置,需要根据电脑的端口实际情况来确定串口数量,不能同设置波特率属性一般,直接在程序里指定唯一值。
string[] ports = SerialPort.GetPortNames();//获取当前计算机的串行端口名的数组。
for(int index =0;index<ports.Length;index++)
{
cb_SerialPortNumber.Items.Add(ports[index]);//添加item
cb_SerialPortNumber.SelectedIndex = index; //设置显示的item索引
}
在官方文档中找到获取当前计算机的串行端口名的数组的函数GetPortNames (),接着使用循环,利用comboBox的Items.Add方法将数组内的元素添加到comboBox中。
<Window x:Class="SerialDebuggingAssistant.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SerialDebuggingAssistant"
Title="串口调试助手" Height="400" Width="600" ResizeMode="CanMinimize" Closing="Window_Closing">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="192*"/>
<RowDefinition Height="82*"/>
<RowDefinition Height="97*"/>
Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="266*"/>
<ColumnDefinition Width="327*"/>
Grid.ColumnDefinitions>
<GroupBox Header="串口参数设置" BorderBrush="Black">
<StackPanel >
<StackPanel Orientation="Horizontal">
<TextBlock Text="串口号" Width="auto" Margin="5" Padding="0,6,0,0"/>
<ComboBox Name="cb_SerialPortNumber" Width="65" Margin="5,10,5,10"/>
<TextBlock Text="波特率" Width="auto" Margin="5" Padding="0,6,0,0"/>
<ComboBox Name="cb_BaudRate" Width="68" Margin="10,10,0,10" SelectedIndex="8">
<ComboBoxItem Content="600"/>
<ComboBoxItem Content="1200"/>
<ComboBoxItem Content="2400"/>
<ComboBoxItem Content="4800"/>
<ComboBoxItem Content="9600"/>
<ComboBoxItem Content="14400"/>
<ComboBoxItem Content="19200"/>
<ComboBoxItem Content="28800"/>
<ComboBoxItem Content="38400"/>
<ComboBoxItem Content="57600"/>
<ComboBoxItem Content="115200"/>
<ComboBoxItem Content="230400"/>
<ComboBoxItem Content="460800"/>
ComboBox>
StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="校验位" Width="auto" Margin="5" Padding="0,6,0,0"/>
<ComboBox Width="65" Margin="5,10,5,10" SelectedIndex="0">
<ComboBoxItem Content="None"/>
ComboBox>
<TextBlock Text="数据位" Width="auto" Margin="5" Padding="0,6,0,0"/>
<ComboBox Width="68" Margin="10,10,0,10" SelectedIndex="0">
<ComboBoxItem Content="8"/>
ComboBox>
StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="停止位" Width="auto" Margin="10,10,0,10" Padding="0,3,0,0"/>
<ComboBox Width="65" Margin="5,10,5,10" SelectedIndex="0">
<ComboBoxItem Content="one"/>
ComboBox>
StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Name="tb_switchStatus" Text="串口为关闭状态" Width="auto" Margin="10" Padding="0,2,0,0"/>
<Ellipse Name="e_status" Fill="#000000" Height="10" Stroke="Black" Margin="5,3,5,0" Width="10" />
<Button Name="bt_SerialSwitch" Content="打开串口" Margin="10" Padding="5,0,5,0" Click="bt_SerialSwitch_Click"/>
StackPanel>
StackPanel>
GroupBox>
<GroupBox Header="接收数据设置" Grid.Row="1" BorderBrush="Black">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Name="ClearReceiveData" Content="清空接收数据" Margin="20" Click="ClearReceiveData_Click"/>
<Button Name="bt_stopSend" Content="停止接收" Margin="20" Click="bt_stopSend_Click"/>
StackPanel>
GroupBox>
<GroupBox Header="发送数据设置" Grid.Row="2" BorderBrush="Black">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Name="bt_ClearSendData" Content="清空发送数据" Margin="20,30,20,25" Click="bt_ClearSendData_Click"/>
<Button Name="bt_send" Content="手动发送" Margin="20,30,20,25" Click="bt_send_Click"/>
StackPanel>
GroupBox>
<GroupBox Header="接收数据区" Grid.Column="1" Grid.RowSpan="2" BorderBrush="Black" Margin="5">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<TextBlock Name="tb_receiveData" TextWrapping="Wrap"/>
ScrollViewer>
GroupBox>
<GroupBox Header="发送数据区" Grid.Row="2" Grid.Column="1" BorderBrush="Black">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<TextBox Name="tb_SendData" TextWrapping="Wrap"/>
ScrollViewer>
GroupBox>
Grid>
Window>
using System.IO.Ports;
public SerialPort _serialPort = new SerialPort ();
(1)“清空接收数据”和“清空发送数据”按钮的点击事件,只需要把对应区域的Text属性设为空,Text = “” 。
private void bt_ClearSendData_Click(object sender, RoutedEventArgs e)//清空发送区域
{
tb_SendData.Text = "";
}
private void ClearReceiveData_Click(object sender, RoutedEventArgs e)//清空接受数据
{
tb_receiveData.Text = "";
}
(1)如数据位、奇偶校验位、停止位和波特率等,在官方文档中说明使用PortName、BaudRate、DataBits、StopBits、Parity分别可以设置串口、波特率、数据位、停止位和校验位,其中串口、波特率是根据实际情况来设定的,数据位、停止位和校验位因为基本上在使用时不会去做更改,所以指定唯一值。
public void initialize()//初始化
{
_serialPort.PortName = cb_SerialPortNumber.SelectedItem.ToString();//串口号
ComboBoxItem seletedItem = (ComboBoxItem)this.cb_BaudRate.SelectedItem;
_serialPort.BaudRate = Convert.ToInt32(seletedItem.Content.ToString());//波特率
_serialPort.DataBits = 8;//数据位
_serialPort.StopBits = StopBits.One;//停止位
_serialPort.Parity = Parity.None;//校验位
}
(1)这里主要实现的功能是打开和关闭串口,添加数据接收事件和取消数据接收事件。开关串口需要使用两个函数open()和close()。
private void bt_SerialSwitch_Click(object sender, RoutedEventArgs e)//串口开关
{
initialize();//初始化
string strContent = this.bt_SerialSwitch.Content.ToString();
if (strContent == "打开串口")
{
_serialPort.Open();
_serialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);//添加数据接收事件
//_serialPort.DataReceived += DataReceivedHandler;
bt_SerialSwitch.Content = "关闭串口";
tb_switchStatus.Text = "串口为打开状态";
bt_send.IsEnabled = true;
bt_stopSend.IsEnabled = true;
e_status.Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF0000"));
}
else
{
_serialPort.DataReceived -= DataReceivedHandler;
_serialPort.Close();
bt_SerialSwitch.Content = "打开串口";
tb_switchStatus.Text = "串口为关闭状态";
e_status.Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#000000"));
bt_send.IsEnabled = false;
bt_stopSend.IsEnabled = false;
}
}
(1)官方文档上说明需要使用SerialErrorReceivedEventHandler(注解原文:“创建SerialErrorReceivedEventHandler委托时,需要标识将处理该事件的方法。若要将事件与事件处理程序关联,请将该委托的一个实例添加到事件中”)。在文档中找到了DataReceived,具体写法是:
SerialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);后来发现可以简写 serialPort1.DataReceived += new DataReceivedHandler; 其中的DataReceivedHandler就是数据接收的具体实现方法。
//在串口打开点击事件中添加
_serialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);//添加数据接收事件
(1)获取数据
public void DataReceivedHandler(object sender,SerialDataReceivedEventArgs e)//读取下位机的数据,显示在textBlock中
{
int len = this._serialPort.BytesToRead;
byte[] buffer = new byte[len];
this._serialPort.Read(buffer, 0, len);
string strData = BitConverter.ToString(buffer, 0, len);
Dispatcher.Invoke(() =>
{
this.tb_receiveData.Text += strData;
this.tb_receiveData.Text += "-";//字符分隔-
});
}
(1)使用Write()方法发送数据
private void bt_send_Click(object sender, RoutedEventArgs e)//发送按钮
{
string SendData = tb_SendData.Text;
byte[] Data = new byte[20];
for (int i = 0; i < SendData.Length / 2; i++)
{
//每次取两位字符组成一个16进制
Data[i] = Convert.ToByte(tb_SendData.Text.Substring(i * 2, 2), 16);
}
this._serialPort.Write(Data, 0, Data.Length);
}
(2)“ 停止接收数据”和“继续接收数据”
private void bt_stopSend_Click(object sender, RoutedEventArgs e)//停止接收数据
{
string strContent = this.bt_stopSend.Content.ToString();
if (strContent == "停止接收")
{
byte[] data = { 0x99 };
_serialPort.DataReceived -= DataReceivedHandler;
this._serialPort.Write(data, 0, data.Length);
bt_stopSend.Content = "继续接收";
}else
{
byte[] data = { 0x66 };
this._serialPort.Write(data, 0, data.Length);
_serialPort.DataReceived += DataReceivedHandler;
bt_stopSend.Content = "停止接收";
}
}
(1)当用户将程序关闭时,如果串口处于开启状态时,应该在关闭程序时将串口关闭。
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
string strContent = this.bt_SerialSwitch.Content.ToString();
if (strContent == "关闭串口")
{
_serialPort.Close();
}
}
(1)我们发现当串口还没被打开时,如果点击“停止接收”和“手动发送”按钮,会出现异常,导致程序无法进行,所以用了这个办法,当串口还没别打开,“停止接收”和“手动发送”按钮的IsEnabled属性就处于false状态,直到串口打开,然后才恢复true状态。
(2)当打开串口后,点击关闭串口按钮时,也会抛异常,并且提示在初始化那一块和关闭串口事件那块,所以我们将这两个地方加了try……catch,让程序不去理他继续运行。
(一)当程序运行,用户将串口号和波特率设置完成,点击“打开串口”按钮时,“接收数据区域”会显示从下位机传来的数据。
(二)当点击“停止接收”按钮时,下位机停止发送数据,“接收数据区域”停止显示,按钮上的文本变成“继续接收”,当再次点击按钮时,下位机继续发送数据,“接收数据区域”继续显示数据,按钮文本变成“停止接收”。
(三)当点击“清空接收数据”和“清空发送数据”时,对应区域的文本将会清空。
(四)当在“发送数据区域”输入“99”,点击“手动发送”按钮时,下位机停止发送数据,“接收数据区域”停止显示。发送“66”时,下位机继续发送数据,“接收数据区域”继续显示数据。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
//上面的是系统自动生成的
using System.IO.Ports;//这是手动添加的
namespace SerialDebuggingAssistant
{
///
/// MainWindow.xaml 的交互逻辑
///
///
public partial class MainWindow : Window
{
public SerialPort _serialPort = new SerialPort ();
public MainWindow()
{
InitializeComponent();
string[] ports = SerialPort.GetPortNames();//获取当前计算机的串行端口名的数组。
for(int index =0;index<ports.Length;index++)
{
cb_SerialPortNumber.Items.Add(ports[index]);//添加item
cb_SerialPortNumber.SelectedIndex = index; //设置显示的item索引
}
bt_send.IsEnabled = false;
bt_stopSend.IsEnabled = false;
}
public void initialize()//初始化
{
//关闭串口时回抛异常
try
{
_serialPort.PortName = cb_SerialPortNumber.SelectedItem.ToString();//串口号
ComboBoxItem seletedItem = (ComboBoxItem)this.cb_BaudRate.SelectedItem;
_serialPort.BaudRate = Convert.ToInt32(seletedItem.Content.ToString());//波特率
_serialPort.DataBits = 8;//数据位
_serialPort.StopBits = StopBits.One;//停止位
_serialPort.Parity = Parity.None;//校验位
}
catch
{
}
}
private void bt_ClearSendData_Click(object sender, RoutedEventArgs e)//清空发送区域
{
tb_SendData.Text = "";
}
private void ClearReceiveData_Click(object sender, RoutedEventArgs e)//清空接受数据
{
tb_receiveData.Text = "";
}
private void bt_SerialSwitch_Click(object sender, RoutedEventArgs e)//串口开关
{
initialize();//初始化
string strContent = this.bt_SerialSwitch.Content.ToString();
if (strContent == "打开串口")
{
try
{
_serialPort.Open();
_serialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);//添加数据接收事件
//_serialPort.DataReceived += DataReceivedHandler;
bt_SerialSwitch.Content = "关闭串口";
tb_switchStatus.Text = "串口为打开状态";
bt_send.IsEnabled = true;
bt_stopSend.IsEnabled = true;
e_status.Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF0000"));
}
catch { }
}
else
{
try
{
_serialPort.DataReceived -= DataReceivedHandler;
_serialPort.Close();
bt_SerialSwitch.Content = "打开串口";
tb_switchStatus.Text = "串口为关闭状态";
e_status.Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#000000"));
bt_send.IsEnabled = false;
bt_stopSend.IsEnabled = false;
}
catch
{
}
}
}
public void DataReceivedHandler(object sender,SerialDataReceivedEventArgs e)//读取下位机的数据,显示在textBlock中
{
int len = this._serialPort.BytesToRead;
byte[] buffer = new byte[len];
this._serialPort.Read(buffer, 0, len);
string strData = BitConverter.ToString(buffer, 0, len);
Dispatcher.Invoke(() =>
{
this.tb_receiveData.Text += strData;
this.tb_receiveData.Text += "-";
});
}
private void bt_send_Click(object sender, RoutedEventArgs e)//发送按钮
{
string SendData = tb_SendData.Text;
byte[] Data = new byte[20];
for (int i = 0; i < SendData.Length / 2; i++)
{
//每次取两位字符组成一个16进制
Data[i] = Convert.ToByte(tb_SendData.Text.Substring(i * 2, 2), 16);
}
this._serialPort.Write(Data, 0, Data.Length);
}
private void bt_stopSend_Click(object sender, RoutedEventArgs e)//停止接收数据
{
string strContent = this.bt_stopSend.Content.ToString();
if (strContent == "停止接收")
{
byte[] data = { 0x99 };
_serialPort.DataReceived -= DataReceivedHandler;
this._serialPort.Write(data, 0, data.Length);
bt_stopSend.Content = "继续接收";
}else
{
byte[] data = { 0x66 };
this._serialPort.Write(data, 0, data.Length);
_serialPort.DataReceived += DataReceivedHandler;
bt_stopSend.Content = "停止接收";
}
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
string strContent = this.bt_SerialSwitch.Content.ToString();
if (strContent == "关闭串口")
{
_serialPort.Close();
}
}
}
}