1、USB调试助手Demo
该Demo使用WPF框架,基于MVVM设计模式,实现USB调试助手,效果如图所示:
实现功能:上位机(USB调试助手)与下位机(ZYNQ)通过USB通信,实现收发数据
实验环境:Visual Studio 2022
控件库:HandyControl 手把手一起使用开源WPF控件HandyControl
USB库:LibUsbDotNet
完整工程:手把手一起使用WPF MVVM制作USB调试助手Demo完整工程文件
2、功能演示
该Demo代码结构如图所示,参照MVVM设计模式完成
项目运行后界面如图所示:
USB设备通过两个ID进行连接,默认ID为随机输入的,故直接点连接USB设备
会弹出没有发现设备,如图所示:
下位机连接正常后,下拉框可以选择相应USB设备,如图所示:
发送和接收的字节数同样可以设置,如图所示:
设置完成后,点击连接USB设备
即可,此时可以打开Bus Hound超级软件总线协议分析器,观察发送和接收数据,如图所示:
在USB发送数据区
输入数据,点击开始发送,可以看到Bus Hound显示已经发送,如图所示,发送数据格式之所以如此,是因为自定义了上位机与下位机的通信协议头帧、校验帧及尾帧:
点击开始接收
,下位机将数据返回至上位机界面,同时Bus Hound显示已经接收的数据,两者一致,如图所示:
再次进行发送和接收数据:
最后,点击断开USB设备
,如图所示:
3、UI界面XAML
UI界面代码MainWindow.xaml如下:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Wpf_USB"
xmlns:hc="https://handyorg.github.io/handycontrol" x:Class="Wpf_USB.MainWindow"
mc:Ignorable="d"
Title="WPF_USB" Height="480" Width="800" ResizeMode="CanMinimize">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160*"/>
<ColumnDefinition Width="160*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="60*"/>
<RowDefinition Height="60*"/>
<RowDefinition Height="60*"/>
<RowDefinition Height="60*"/>
</Grid.RowDefinitions>
<GroupBox Header="USB设备信息" BorderBrush="Black" FontSize="20" FontWeight="Bold" Margin="5,5,5,5" Grid.RowSpan="2">
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock TextWrapping="Wrap" Text="VendorID:" Width="auto" RenderTransformOrigin="0.478,1.336" Padding="0,10,0,0" Height="44" Margin="10"/>
<ComboBox Width="177" Margin="15,10,5,10" ItemsSource="{Binding UsbPar.VendorID}" SelectedItem="{Binding CuPar.VendorID}" IsEnabled="{Binding CuPar.EnableSelect}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock TextWrapping="Wrap" Text="ProductID:" Width="auto" RenderTransformOrigin="0.478,1.336" Padding="0,10,0,0" Height="44" Margin="10"/>
<ComboBox Width="177" Margin="10,10,5,10" ItemsSource="{Binding UsbPar.ProductID}" SelectedItem="{Binding CuPar.ProductID}" IsEnabled="{Binding CuPar.EnableSelect}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="连接USB设备" Margin="20,0,10,0" FontSize="16" Height="38" Command="{Binding OpenUsbDev}"/>
<Button Content="断开USB设备" Margin="20,0,10,0" FontSize="16" Height="38" Command="{Binding CloseUsbDev}"/>
</StackPanel>
</StackPanel>
</GroupBox>
<GroupBox Header="USB接收数据区" FontSize="20" FontWeight="Bold" BorderBrush="#FF7ED866" Margin="5,5,5,5" Grid.Row="2" Grid.RowSpan="2">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<TextBox Name="ReceiveData" TextWrapping="Wrap" Text="{Binding CuPar.ReadDataString}" FontWeight="Normal" FontSize="16"/>
</ScrollViewer>
</GroupBox>
<GroupBox Header="接收数据设置" FontSize="20" FontWeight="Bold" Margin="5,5,5,5" BorderBrush="#FF5570C7" Grid.Column="1">
<StackPanel Orientation="Horizontal">
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="BytesRead:" Padding="0,15,0,0"/>
<ComboBox Width="130" Height="40" ItemsSource="{Binding UsbPar.BytesRead}" SelectedItem="{Binding CuPar.BytesRead}" IsEnabled="{Binding CuPar.EnableSelect}"/>
<Button Content="开始接收" FontSize="20" Height="58" Margin="5" Command="{Binding UsbDevRead}"/>
</StackPanel>
</GroupBox>
<GroupBox Header="发送数据设置" FontSize="20" FontWeight="Bold" Margin="5,5,5,5" BorderBrush="#FF5D29A0" Grid.Column="1" Grid.Row="1">
<StackPanel Orientation="Horizontal">
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="BytesWrite:" Padding="0,15,0,0"/>
<ComboBox Width="130" Height="40" ItemsSource="{Binding UsbPar.BytesWrite}" SelectedItem="{Binding CuPar.BytesWrite}" IsEnabled="{Binding CuPar.EnableSelect}"/>
<Button Content="开始发送" FontSize="20" Height="58" Margin="5" Command="{Binding UsbDevWrite}"/>
</StackPanel>
</GroupBox>
<GroupBox Header="USB发送数据区" FontSize="20" FontWeight="Bold" Margin="5,5,5,5" BorderBrush="#FFA42D84" Grid.Column="1" Grid.RowSpan="2" Grid.Row="2">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<TextBox Name="SendData" TextWrapping="Wrap" Text="{Binding CuPar.WriteData}" FontWeight="Normal" FontSize="16"/>
</ScrollViewer>
</GroupBox>
</Grid>
</Window>
由于使用了开源控件库:HandyControl,因此App.xaml代码需要如下编写:
<Application x:Class="Wpf_USB.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Wpf_USB"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml" />
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
4、后台逻辑CS
后台MainWindow.xaml.cs代码如下:
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 Wpf_USB.ViewModels;
namespace Wpf_USB
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
}
USB各参数初始化类,即界面初始化的参数,UsbParameter.cs代码如下:
using LibUsbDotNet.Main;
using LibUsbDotNet;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Wpf_USB.ViewModels
{
class UsbParameter : NotificationObject
{
private ObservableCollection<int> vendorid = new ObservableCollection<int>() { 1234 };
public ObservableCollection<int> VendorID
{
get { return vendorid; }
set
{
vendorid = value;
this.RaisePropertyChanged("VendorID");
}
}
private ObservableCollection<int> productid = new ObservableCollection<int>() { 1 };
public ObservableCollection<int> ProductID
{
get { return productid; }
set
{
productid = value;
this.RaisePropertyChanged("ProductID");
}
}
private ObservableCollection<int> bytesread = new ObservableCollection<int>() { 512, 1024, 2048, 4096, 8192, 16384 };
public ObservableCollection<int> BytesRead
{
get { return bytesread; }
set
{
bytesread = value;
this.RaisePropertyChanged("BytesRead");
}
}
private ObservableCollection<int> byteswrite = new ObservableCollection<int>() { 512, 1024, 2048, 4096, 8192, 16384 };
public ObservableCollection<int> BytesWrite
{
get { return byteswrite; }
set
{
byteswrite = value;
this.RaisePropertyChanged("BytesWrite");
}
}
public void UsbDeviceFind()
{
UsbRegDeviceList allDevices = UsbDevice.AllDevices;
if (allDevices != null)
{
foreach (UsbRegistry usbRegistry in allDevices)
{
VendorID.Add(usbRegistry.Vid);
ProductID.Add(usbRegistry.Pid);
}
UsbDevice.Exit();
}
else { MessageBox.Show("UsbDevive Not Found."); }
}
}
}
USB更新数据类,CurrentParameter.cs代码如下:
using LibUsbDotNet;
using LibUsbDotNet.Main;
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows;
using System.Runtime.Remoting.Messaging;
using System.Collections.ObjectModel;
using System.Reflection;
using Wpf_USB.Models;
namespace Wpf_USB.ViewModels
{
class CurrentParameter : NotificationObject
{
public CurrentParameter()
{
GloVar = new GlobalVariable();
DataPro = new DataProcess();
}
private GlobalVariable gloVar;
public GlobalVariable GloVar
{
get { return gloVar; }
set
{
gloVar = value;
}
}
private DataProcess dataPro;
public DataProcess DataPro
{
get { return dataPro; }
set
{
dataPro = value;
}
}
private int vendorid;
public int VendorID
{
get { return vendorid; }
set
{
vendorid = value;
this.RaisePropertyChanged("VendorID");
}
}
private int productid;
public int ProductID
{
get { return productid; }
set
{
productid = value;
this.RaisePropertyChanged("ProductID");
}
}
private int bytesread;
public int BytesRead
{
get { return bytesread; }
set
{
bytesread = value;
this.RaisePropertyChanged("BytesRead");
}
}
private int byteswrite;
public int BytesWrite
{
get { return byteswrite; }
set
{
byteswrite = value;
this.RaisePropertyChanged("BytesWrite");
}
}
private UsbDevice usbDevice;
private UsbEndpointReader epReader;
private UsbEndpointWriter epWriter;
public UsbDevice UsbDevice
{
get { return usbDevice; }
set { usbDevice = value; this.RaisePropertyChanged("UsbDevice"); }
}
private byte[] readData;
private string writeData;
private string readDataString;
private bool isOpen;
private bool enableSelect = true;
public bool EnableSelect
{
get { return enableSelect; }
set { enableSelect = value; this.RaisePropertyChanged("EnableSelect"); }
}
public byte[] ReadData
{
get { return readData; }
set { readData = value; this.RaisePropertyChanged("ReadData"); }
}
public string ReadDataString
{
get { return readDataString; }
set { readDataString = value; this.RaisePropertyChanged("ReadDataString"); }
}
public string WriteData
{
get { return writeData; }
set { writeData = value; this.RaisePropertyChanged("WriteData"); }
}
public bool IsOpen
{
get { return isOpen; }
set { isOpen = value; this.RaisePropertyChanged("IsOpen"); }
}
public bool UsbDevicesOpen()
{
if (usbDevice != null && usbDevice.IsOpen)
{
return UsbDevicesClose();
}
try
{
UsbDeviceFinder UsbFinder = new UsbDeviceFinder(this.VendorID, this.ProductID);
usbDevice = UsbDevice.OpenUsbDevice(UsbFinder);
if (usbDevice == null) throw new Exception("Device Not Found.");
IUsbDevice wholeUsbDevice = usbDevice as IUsbDevice;
if (!ReferenceEquals(wholeUsbDevice, null))
{
wholeUsbDevice.SetConfiguration(1);
wholeUsbDevice.ClaimInterface(0);
}
epReader = usbDevice.OpenEndpointReader(ReadEndpointID.Ep01);
epWriter = usbDevice.OpenEndpointWriter(WriteEndpointID.Ep01);
EnableSelect = false;
if (usbDevice.IsOpen)
{
return IsOpen = true;
}
else
{
return IsOpen = false;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return IsOpen = false;
}
public bool UsbDevicesClose()
{
try
{
EnableSelect = true;
if (usbDevice == null) throw new Exception("Device Not Found.");
else
{
if (usbDevice.IsOpen)
{
IUsbDevice wholeUsbDevice = usbDevice as IUsbDevice;
if (!ReferenceEquals(wholeUsbDevice, null))
{
// Release interface #0.
wholeUsbDevice.ReleaseInterface(0);
}
usbDevice.Close();
return IsOpen = false;
}
usbDevice = null;
UsbDevice.Exit();
return IsOpen = false;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return IsOpen = false;
}
}
public void UsbDevicesRead()
{
try
{
if (usbDevice == null) throw new Exception("Device Not Found.");
else
{
int bytesReadCount = BytesRead;
readData = new byte[bytesReadCount];
ErrorCode ec = epReader.Read(ReadData, 2000, out bytesReadCount);
this.DataPro.ReadDataCRC(ReadData);
ReadDataString += GloVar.CMD_H.ToString("X2") + " ";
ReadDataString += GloVar.CMD_L.ToString("X2") + " ";
ReadDataString += GloVar.RESULT_F.ToString("X2") + " ";
for (int i = 0; i < GloVar.ReadValidData.Length; i++)
ReadDataString += GloVar.ReadValidData[i].ToString("X2") + " ";
if (bytesReadCount == 0) MessageBox.Show("No More Bytes.");
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
public void UsbDevicesWrite()
{
try
{
if (usbDevice == null) throw new Exception("Device Not Found.");
else
{
if (!String.IsNullOrEmpty(WriteData))
{
int bytesWriteCount = BytesWrite;
DataPro.WriteDataCRC(WriteData);
ErrorCode ec = epWriter.Write(GloVar.WriteValidData, 2000, out bytesWriteCount);
if (ec != ErrorCode.None) MessageBox.Show("Write Fail.");
}
else
{
MessageBox.Show("WriteData Is Empty.");
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
}
响应数据实时更新类,NotificationObject.cs代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Wpf_USB.ViewModels
{
class NotificationObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
按键命令类,DelegateCommand.cs代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Wpf_USB.ViewModels
{
class DelegateCommand : ICommand
{
public bool CanExecute(object parameter)
{
if (CanExecuteFunc == null)
return true;
return this.CanExecuteFunc(parameter);
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if (ExecuteAction == null)
{
return;
}
this.ExecuteAction(parameter);
}
public Action<object> ExecuteAction { get; set; }
public Func<object, bool> CanExecuteFunc { get; set; }
}
}
MainWindowViewModel.cs代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Wpf_USB.ViewModels
{
class MainWindowViewModel : NotificationObject
{
public MainWindowViewModel()
{
UsbPar = new UsbParameter();
CuPar = new CurrentParameter();
this.UsbPar.UsbDeviceFind();
CuPar.VendorID = UsbPar.VendorID[0];
CuPar.ProductID = UsbPar.ProductID[0];
CuPar.BytesRead = UsbPar.BytesRead[3];
CuPar.BytesWrite = UsbPar.BytesWrite[3];
this.OpenUsbDev = new DelegateCommand();
this.OpenUsbDev.ExecuteAction = new Action<object>(this.OpenUsb);
this.CloseUsbDev = new DelegateCommand();
this.CloseUsbDev.ExecuteAction = new Action<object>(this.CloseUsb);
this.UsbDevRead = new DelegateCommand();
this.UsbDevRead.ExecuteAction = new Action<object>(this.UsbRead);
this.UsbDevWrite = new DelegateCommand();
this.UsbDevWrite.ExecuteAction = new Action<object>(this.UsbWrite);
}
private UsbParameter usbPar;
public UsbParameter UsbPar
{
get { return usbPar; }
set
{
usbPar = value;
this.RaisePropertyChanged("UsbPar");
}
}
private CurrentParameter cuPar;
public CurrentParameter CuPar
{
get { return cuPar; }
set
{
cuPar = value;
this.RaisePropertyChanged("CuPar");
}
}
public DelegateCommand OpenUsbDev { get; set; }
private void OpenUsb(object parameter)
{
this.CuPar.UsbDevicesOpen();
}
public DelegateCommand CloseUsbDev { get; set; }
private void CloseUsb(object parameter)
{
this.CuPar.UsbDevicesClose();
}
public DelegateCommand UsbDevRead { get; set; }
private void UsbRead(object parameter)
{
this.CuPar.UsbDevicesRead();
}
public DelegateCommand UsbDevWrite { get; set; }
private void UsbWrite(object parameter)
{
this.CuPar.UsbDevicesWrite();
}
}
}
数据处理类,包括自定义的通信协议,DataProcess.cs代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using Wpf_USB.ViewModels;
namespace Wpf_USB.Models
{
class DataProcess : NotificationObject
{
public DataProcess()
{
GloVar = new GlobalVariable();
}
private GlobalVariable gloVar;
public GlobalVariable GloVar
{
get { return gloVar; }
set
{
gloVar = value;
this.RaisePropertyChanged("GloVar");
}
}
public void ReadDataCRC(byte[] ReadData)
{
int CheckoutSum = 0;
int DataLength = ReadData[2] * 256 + ReadData[3];
if (ReadData[0].ToString("X") != "7E" || ReadData[1].ToString("X") != "7E")
{
GloVar.FrameData_F = true;
MessageBox.Show("Data Frame Header Error.");
}
for (int i=0; i< DataLength; i++)
{
CheckoutSum += ReadData[i + 4];
}
if (Convert.ToByte(CheckoutSum & 0xFF) != Convert.ToByte(ReadData[DataLength+4]))
{
GloVar.FrameData_F = true;
MessageBox.Show("Data Frame Check Error.");
}
GloVar.CMD_H = ReadData[4];
GloVar.CMD_L = ReadData[5];
GloVar.RESULT_F = ReadData[6];
GloVar.ReadValidData = new byte[DataLength-3];
for (int i = 0; i < (DataLength-3); i++)
{
GloVar.ReadValidData[i] = ReadData[i+7];
}
if (ReadData[DataLength + 5].ToString("X") != "45" || ReadData[DataLength + 6].ToString("X") != "4E" || ReadData[DataLength + 7].ToString("X") != "44")
{
GloVar.FrameData_F = true;
MessageBox.Show("Data Frame End Error.");
}
}
public void WriteDataCRC(string WriteData)
{
int CheckoutSum = 0 ;
string WriteDataHex = WriteData.Replace(" ", "");
WriteDataHex += WriteDataHex.Length % 2 != 0 ? "0" : "";
int WriteValidDataLength = WriteDataHex.Length / 2;
GloVar.WriteValidData = new byte[WriteValidDataLength+8];
GloVar.WriteValidData[0] = 0x7E;
GloVar.WriteValidData[1] = 0x7E;
GloVar.WriteValidData[2] = Convert.ToByte((WriteValidDataLength >> 8) & 0xFF);
GloVar.WriteValidData[3] = Convert.ToByte(WriteValidDataLength & 0xFF);
for (int i=0; i<WriteValidDataLength; i++)
{
GloVar.WriteValidData[4+i] = Convert.ToByte(WriteDataHex.Substring(i * 2, 2), 16);
CheckoutSum += GloVar.WriteValidData[4+i];
}
GloVar.WriteValidData[4 + WriteValidDataLength] = Convert.ToByte(CheckoutSum & 0xFF);
GloVar.WriteValidData[5 + WriteValidDataLength] = 0x45;
GloVar.WriteValidData[6 + WriteValidDataLength] = 0x4E;
GloVar.WriteValidData[7 + WriteValidDataLength] = 0x44;
}
}
}
全局变量类,GlobalVariable.cs代码如下:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Wpf_USB.ViewModels;
namespace Wpf_USB.Models
{
class GlobalVariable : NotificationObject
{
private string globalSystemVersion = "1.0.0";
public string GlobalSystemVersion
{
get { return globalSystemVersion; }
set
{
globalSystemVersion = value;
this.RaisePropertyChanged("GlobalSystemVersion");
}
}
private string globalUpdateTime = "2023.7.27";
public string GlobalUpdateTime
{
get { return globalUpdateTime; }
set
{
globalUpdateTime = value;
this.RaisePropertyChanged("GlobalUpdateTime");
}
}
private static int cMD_H;
public int CMD_H
{
get { return cMD_H; }
set
{
cMD_H = value;
this.RaisePropertyChanged("CMD_H");
}
}
private static int cMD_L;
public int CMD_L
{
get { return cMD_L; }
set
{
cMD_L = value;
this.RaisePropertyChanged("CMD_L");
}
}
private static int rESULT_F;
public int RESULT_F
{
get { return rESULT_F; }
set
{
rESULT_F = value;
this.RaisePropertyChanged("RESULT_F");
}
}
public static byte[] readValidData;
public byte[] ReadValidData
{
get { return readValidData; }
set
{
readValidData = value;
this.RaisePropertyChanged("ReadValidData");
}
}
public static byte[] writeValidData;
public byte[] WriteValidData
{
get { return writeValidData; }
set
{
writeValidData = value;
this.RaisePropertyChanged("WriteValidData");
}
}
public static bool frameData_F = false;
public bool FrameData_F
{
get { return frameData_F; }
set
{
frameData_F = value;
this.RaisePropertyChanged("FrameData_F");
}
}
}
}
该Demo实现过程中,参考了一些大佬(CSDN和Github)的代码,在此表示感谢
希望本文对大家有帮助,上文若有不妥之处,欢迎指正
分享决定高度,学习拉开差距