using System.Collections.ObjectModel; using System.Globalization; using System.Windows; using System.Windows.Controls; using System.Windows.Threading; using SDCAcqDacTester.Can; namespace SDCAcqDacTester; public partial class MainWindow : Window { private const uint HeartbeatId = 0x6A0; private const uint AdcId = 0x6A1; private const uint PwmId = 0x6A2; private const uint DacCommandId = 0x6A3; private const uint DacAckId = 0x6A4; private const uint AssiCommandId = 0x6A5; private const uint GpioId = 0x6A6; private readonly ObservableCollection _rows = []; private readonly DispatcherTimer _holdTimer; private ControlCanTransport? _can; private bool _holdDac1; private bool _holdDac2; public MainWindow() { InitializeComponent(); LogGrid.ItemsSource = _rows; _holdTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(50) }; _holdTimer.Tick += HoldTimer_Tick; } private void ConnectButton_Click(object sender, RoutedEventArgs e) { try { if (!uint.TryParse(DeviceIndexBox.Text, out uint index)) throw new InvalidOperationException("设备索引必须为非负整数。"); _can = new ControlCanTransport(); _can.FrameReceived += Can_FrameReceived; _can.ReceiveError += Can_ReceiveError; _can.Open(index, (uint)ChannelBox.SelectedIndex); ConnectionText.Text = $"已连接:USBCAN-II / CAN{ChannelBox.SelectedIndex + 1}"; ConnectionText.Foreground = System.Windows.Media.Brushes.LightGreen; ConnectButton.IsEnabled = false; DisconnectButton.IsEnabled = true; AddLog("SYS", "-", [], "已连接,500 kbit/s 正常模式。"); } catch (Exception ex) { ShowError(ex.Message); } } private void DisconnectButton_Click(object sender, RoutedEventArgs e) => Disconnect(); private void SendDac1Once_Click(object sender, RoutedEventArgs e) => SendDac(1, ReadVolts(Dac1Box)); private void SendDac2Once_Click(object sender, RoutedEventArgs e) => SendDac(2, ReadVolts(Dac2Box)); private void HoldDac1Button_Click(object sender, RoutedEventArgs e) { _holdDac1 = !_holdDac1; HoldDac1Button.Content = _holdDac1 ? "停止保持" : "开始保持"; UpdateHoldTimer(); } private void HoldDac2Button_Click(object sender, RoutedEventArgs e) { _holdDac2 = !_holdDac2; HoldDac2Button.Content = _holdDac2 ? "停止保持" : "开始保持"; UpdateHoldTimer(); } private void HoldTimer_Tick(object? sender, EventArgs e) { try { if (_holdDac1) SendDac(1, ReadVolts(Dac1Box)); if (_holdDac2) SendDac(2, ReadVolts(Dac2Box)); } catch (Exception ex) { _holdDac1 = _holdDac2 = false; HoldDac1Button.Content = HoldDac2Button.Content = "开始保持"; UpdateHoldTimer(); ShowError(ex.Message); } } private void Assi_Click(object sender, RoutedEventArgs e) { if (sender is not Button { Tag: string value } || !byte.TryParse(value, out byte state)) return; Send(AssiCommandId, [state], $"ASSI 状态={state}"); } private void SendRaw_Click(object sender, RoutedEventArgs e) { try { uint id = uint.Parse(RawIdBox.Text.Trim().Replace("0x", "", StringComparison.OrdinalIgnoreCase), NumberStyles.HexNumber, CultureInfo.InvariantCulture); byte[] data = string.IsNullOrWhiteSpace(RawDataBox.Text) ? [] : RawDataBox.Text.Split([' ', ',', ';'], StringSplitOptions.RemoveEmptyEntries) .Select(x => byte.Parse(x.Replace("0x", "", StringComparison.OrdinalIgnoreCase), NumberStyles.HexNumber, CultureInfo.InvariantCulture)).ToArray(); Send(id, data, "手工帧"); } catch (Exception ex) { ShowError($"手工帧格式错误:{ex.Message}"); } } private void SendDac(byte dac, ushort millivolts) { Send(DacCommandId, [dac, (byte)(millivolts & 0xFF), (byte)(millivolts >> 8)], $"DAC{dac}={millivolts} mV"); } private void Send(uint id, byte[] data, string description) { try { if (_can is not { IsOpen: true }) throw new InvalidOperationException("请先连接 CAN。 "); _can.SendStandard(id, data); AddLog("TX", $"0x{id:X3}", data, description); } catch (Exception ex) { ShowError(ex.Message); } } private static ushort ReadVolts(TextBox box) { if (!decimal.TryParse(box.Text, NumberStyles.Number, CultureInfo.InvariantCulture, out decimal volts) && !decimal.TryParse(box.Text, NumberStyles.Number, CultureInfo.CurrentCulture, out volts)) throw new InvalidOperationException("DAC 电压必须是 0 - 5 V 的数值。"); if (volts is < 0 or > 5) throw new InvalidOperationException("DAC 电压范围为 0 - 5 V。"); return (ushort)Math.Round(volts * 1000m, MidpointRounding.AwayFromZero); } private void Can_FrameReceived(object? sender, CanFrame frame) => Dispatcher.Invoke(() => { string description = Decode(frame); AddLog("RX", frame.IsExtended ? $"0x{frame.Id:X8}" : $"0x{frame.Id:X3}", frame.Data, description, frame.Timestamp); }); private void Can_ReceiveError(object? sender, string error) => Dispatcher.Invoke(() => ShowError($"接收异常:{error}")); private string Decode(CanFrame frame) { var d = frame.Data; switch (frame.Id) { case HeartbeatId when d.Length >= 8: string heartbeat = $"板号=0x{d[0]:X2} 协议={d[1]} 序号={U16(d, 2)} 运行={U32(d, 4)} ms"; HeartbeatValue.Text = heartbeat; return heartbeat; case AdcId when d.Length >= 2: string adc = $"{U16(d, 0)} mV ({U16(d, 0) / 1000.0:F3} V)"; AdcValue.Text = adc; return adc; case PwmId when d.Length >= 8: string pwm = string.Join(" / ", Enumerable.Range(0, 4).Select(i => $"{U16(d, i * 2)} Hz")); PwmValue.Text = pwm; return pwm; case DacAckId when d.Length >= 4: string ack = $"DAC{d[1]} {(d[0] == 1 ? "OK" : "ERR")} = {U16(d, 2)} mV"; DacAckValue.Text = ack; return ack; case GpioId when d.Length >= 8: string gpio = $"状态=0x{U16(d, 0):X4} 变化=0x{U16(d, 2):X4} 时间={U32(d, 4)} ms"; GpioValue.Text = gpio; return gpio; default: return "未定义帧"; } } private static ushort U16(byte[] data, int offset) => (ushort)(data[offset] | (data[offset + 1] << 8)); private static uint U32(byte[] data, int offset) => (uint)(data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24)); private void AddLog(string direction, string id, byte[] data, string description, DateTime? timestamp = null) { _rows.Insert(0, new LogRow((timestamp ?? DateTime.Now).ToString("HH:mm:ss.fff"), direction, id, data.Length.ToString(), BitConverter.ToString(data).Replace('-', ' '), description)); while (_rows.Count > 500) _rows.RemoveAt(_rows.Count - 1); } private void UpdateHoldTimer() { if (_holdDac1 || _holdDac2) _holdTimer.Start(); else _holdTimer.Stop(); } private void Disconnect() { _holdDac1 = _holdDac2 = false; _holdTimer.Stop(); _can?.Dispose(); _can = null; ConnectionText.Text = "未连接"; ConnectionText.Foreground = System.Windows.Media.Brushes.Orange; ConnectButton.IsEnabled = true; DisconnectButton.IsEnabled = false; } private void ShowError(string message) { AddLog("ERR", "-", [], message); MessageBox.Show(message, "SDC-ACQ_DAC 测试工具", MessageBoxButton.OK, MessageBoxImage.Warning); } private void Window_Closing(object? sender, System.ComponentModel.CancelEventArgs e) => Disconnect(); } public sealed record LogRow(string Time, string Direction, string Id, string Dlc, string Data, string Description);