基于C#和周立功USBCAN设备的完整上位机开发示例
一、环境配置
-
驱动安装
- 下载安装ZLG官方驱动(需区分x86/x64)
- 确保
zlgcan.dll位于应用程序目录或系统PATH
-
NuGet依赖
dotnet add package CanKit.Core dotnet add package CanKit.Adapter.ZLG
二、核心代码实现
1. 设备管理类(DeviceManager.cs)
using CanKit.Core;
using CanKit.Core.Definitions;
using CanKit.Adapter.ZLG;
using System;
using System.Threading.Tasks;
public class DeviceManager : IDisposable
{
private ICanBus _bus;
private readonly string _deviceId = "zlg://ZCAN_USBCANFD_200U?index=0#ch0";
private const int BaudRate = 500_000; // 500kbps
public event Action<CanFrame> FrameReceived;
public event Action<string> StatusChanged;
public async Task ConnectAsync()
{
try
{
_bus = await CanBus.OpenAsync(_deviceId, cfg => cfg
.TimingClassic(BaudRate)
.EnableErrorInfo()
.SetAsyncBufferCapacity(512));
_bus.FrameReceived += OnFrameReceived;
StatusChanged?.Invoke("设备已连接");
}
catch (Exception ex)
{
HandleError($"连接失败: {ex.Message}");
}
}
private void OnFrameReceived(object sender, CanFrameReceivedEventArgs e)
{
FrameReceived?.Invoke(e.Frame);
}
public async Task SendTestFrameAsync()
{
var frame = new CanClassicFrame(
id: 0x123,
data: new byte[] { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 },
timestamp: DateTimeOffset.Now.ToUnixTimeMilliseconds()
);
await _bus.TransmitAsync(frame);
}
public void Disconnect()
{
_bus?.Close();
StatusChanged?.Invoke("设备已断开");
}
private void HandleError(string message)
{
StatusChanged?.Invoke($"错误: {message}");
// 记录日志到文件
File.AppendAllText("error.log", $"{DateTime.Now}: {message}\n");
}
public void Dispose()
{
_bus?.Dispose();
}
}
三、用户界面实现(WPF示例)
1. 主窗口XAML
<Window x:Class="USBCANApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Title="USBCAN上位机" Height="600" Width="1200">
<DockPanel>
<!-- 状态栏 -->
<StatusBar DockPanel.Dock="Bottom">
<TextBlock Text="{Binding Status}" Margin="5"/>
</StatusBar>
<!-- 数据监控区 -->
<TabControl>
<TabItem Header="实时数据">
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Frames}">
<DataGrid.Columns>
<DataGridTextColumn Header="时间戳" Binding="{Binding Timestamp}"/>
<DataGridTextColumn Header="ID" Binding="{Binding Id:X}"/>
<DataGridTextColumn Header="数据" Binding="{Binding Data}"/>
</DataGrid.Columns>
</DataGrid>
</TabItem>
</TabControl>
<!-- 控制面板 -->
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" DockPanel.Dock="Top">
<Button Content="连接设备" Command="{Binding ConnectCommand}"/>
<Button Content="发送测试帧" Command="{Binding SendTestCommand}"/>
<Button Content="断开连接" Command="{Binding DisconnectCommand}"/>
</StackPanel>
</DockPanel>
</Window>
2. ViewModel实现
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class MainViewModel : INotifyPropertyChanged
{
private readonly DeviceManager _deviceManager = new DeviceManager();
public ObservableCollection<CanFrame> Frames { get; } = new();
public string Status { get; private set; } = "未连接";
public ICommand ConnectCommand => new RelayCommand(async () => await ConnectAsync());
public ICommand SendTestCommand => new RelayCommand(async () => await SendTestAsync());
public ICommand DisconnectCommand => new RelayCommand(() => Disconnect());
public MainViewModel()
{
_deviceManager.FrameReceived += OnFrameReceived;
_deviceManager.StatusChanged += OnStatusChanged;
}
private async Task ConnectAsync()
{
await _deviceManager.ConnectAsync();
Status = "已连接";
}
private async Task SendTestAsync()
{
await _deviceManager.SendTestFrameAsync();
}
private void Disconnect()
{
_deviceManager.Disconnect();
Status = "已断开";
}
private void OnFrameReceived(CanFrame frame)
{
Frames.Add(frame);
}
private void OnStatusChanged(string status)
{
Status = status;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
四、关键功能扩展
1. CAN FD支持
// 修改连接配置
var config = new ZCANChannelConfig
{
canfd = new ZCAN_FDConfig
{
mode = ZCAN_FDMode.FD,
nominal_bitrate = 5_000_000, // 5Mbps
data_bitrate = 20_000_000 // 20Mbps
}
};
await _bus.OpenAsync(_deviceId, config);
2. 数据滤波配置
// 添加范围滤波
_config.RangeFilter = new RangeFilter
{
StartId = 0x100,
EndId = 0x1FF,
FilterType = FilterType.Standard
};
// 或使用掩码滤波
_config.MaskFilter = new MaskFilter
{
Acr = 0x18FF50E5,
Amr = 0x1FFFFF00
};
3. 周期发送任务
var periodicTask = _bus.SchedulePeriodic(
new CanClassicFrame(0x321, new byte[] { 0xAA, 0x55 }),
interval: TimeSpan.FromMilliseconds(100)
);
// 停止周期任务
periodicTask.Stop();
五、错误处理机制
1. 异常捕获
try
{
await _bus.TransmitAsync(frame);
}
catch (CanTimeoutException ex)
{
LogError($"发送超时: {ex.Message}");
}
catch (CanProtocolException ex)
{
LogError($"协议错误: {ex.Message}");
}
2. 错误帧订阅
_bus.ErrorFrameReceived += (sender, error) =>
{
var errorInfo = new StringBuilder();
errorInfo.AppendLine($"错误码: 0x{error.ErrorInfo.ErrorFlag:X4}");
errorInfo.AppendLine($"错误类型: {error.ErrorInfo.ErrorType}");
errorInfo.AppendLine($"时间戳: {error.Timestamp}");
File.AppendAllText("can_errors.log", errorInfo.ToString());
};
六、性能优化
-
双缓冲队列
private ConcurrentQueue<CanFrame> _buffer = new(); // 接收线程 Task.Run(() => { while (true) { var frame = _bus.Receive(); _buffer.Enqueue(frame); } }); -
硬件加速
_bus.SetHardwareFilter(new HardwareFilter { FilterType = FilterType.Standard, StartId = 0x100, EndId = 0x1FF });
七、部署与测试
1. 安装包配置
<!-- Inno Setup脚本 -->
[Files]
Source: "USBCANApp.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "zlgcan.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "error.log"; DestDir: "{app}"; Flags: ignoreversion
[Icons]
Name: "{group}\USBCAN监控"; Filename: "{app}\USBCANApp.exe"
2. 压力测试
// 模拟高负载测试
Parallel.For(0, 1000, i =>
{
var task = _bus.TransmitAsync(new CanClassicFrame(
id: 0x200,
data: new byte[8] { 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55 }
));
});
参考代码 基于C#的周立功USBCAN上位机程序例程 www.youwenfan.com/contentcnr/112207.html
八、扩展功能建议
-
DBC文件解析
集成CANdb++解析库,实现信号值自动解码
-
Web监控接口
使用ASP.NET Core构建REST API提供数据访问
-
OPC UA集成
通过OPC Foundation库实现工业协议对接