public partial class MainForm : Form{ private readonly SerialPort _port = new SerialPort(); private long _rxCount; private long _txCount; // 用于在 HEX显示 关闭时累积可打印字符 private readonly StringBuilder _asciiBuf = new StringBuilder(); publicMainForm() { InitializeComponent(); WireEvents(); PopulateBaudRates(); PopulateDataBits(); PopulateParity(); PopulateStopBits(); RefreshPorts(); UpdateConnectionState(); } // ===== 事件绑定 ===== privatevoidWireEvents() { btnRefresh.Click += (s, e) => RefreshPorts(); btnOpenClose.Click += (s, e) => ToggleConnection(); btnSend.Click += (s, e) => DoSend(); btnClearTx.Click += (s, e) => txtSend.Clear(); btnClearRx.Click += (s, e) => { txtReceive.Clear(); _asciiBuf.Clear(); }; btnSaveRx.Click += (s, e) => SaveReceived(); btnResetCount.Click += (s, e) => { _rxCount = 0; _txCount = 0; UpdateCounters(); }; chkRxHex.CheckedChanged += (s, e) => { _asciiBuf.Clear(); }; chkAutoWrap.CheckedChanged += (s, e) => { txtReceive.WordWrap = chkAutoWrap.Checked; }; chkAutoSend.CheckedChanged += (s, e) => { if (chkAutoSend.Checked) { if (!_port.IsOpen) { chkAutoSend.Checked = false; MessageBox.Show("请先打开串口再启用自动发送。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } tmrAutoSend.Interval = (int)numInterval.Value; tmrAutoSend.Start(); } else { tmrAutoSend.Stop(); } }; numInterval.ValueChanged += (s, e) => { if (tmrAutoSend.Enabled) { tmrAutoSend.Interval = (int)numInterval.Value; } }; tmrAutoSend.Tick += (s, e) => DoSend(); txtSend.KeyDown += (s, e) => { if (e.Control && e.KeyCode == Keys.Enter) { DoSend(); e.SuppressKeyPress = true; } }; _port.DataReceived += OnDataReceived; this.FormClosing += (s, e) => { if (_port.IsOpen) { try { _port.Close(); } catch { } } }; } // ===== 下拉框填充 ===== privatevoidPopulateBaudRates() { cmbBaud.Items.Clear(); int[] rates = { 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600 }; foreach (var r in rates) cmbBaud.Items.Add(r); cmbBaud.SelectedItem = 115200; } privatevoidPopulateDataBits() { cmbDataBits.Items.Clear(); foreach (var d in new[] { 5, 6, 7, 8 }) cmbDataBits.Items.Add(d); cmbDataBits.SelectedItem = 8; } privatevoidPopulateParity() { cmbParity.Items.Clear(); cmbParity.Items.Add("None"); cmbParity.Items.Add("Odd"); cmbParity.Items.Add("Even"); cmbParity.Items.Add("Mark"); cmbParity.Items.Add("Space"); cmbParity.SelectedIndex = 0; } privatevoidPopulateStopBits() { cmbStopBits.Items.Clear(); cmbStopBits.Items.Add("1"); cmbStopBits.Items.Add("1.5"); cmbStopBits.Items.Add("2"); cmbStopBits.SelectedIndex = 0; } privatevoidRefreshPorts() { string prev = cmbPort.SelectedItem?.ToString() ?? ""; cmbPort.Items.Clear(); try { var names = SerialPort.GetPortNames(); Array.Sort(names, StringComparer.OrdinalIgnoreCase); foreach (var n in names) cmbPort.Items.Add(n); if (cmbPort.Items.Count == 0) { cmbPort.Items.Add("COM1"); } if (cmbPort.Items.Contains(prev)) cmbPort.SelectedItem = prev; else if (cmbPort.Items.Count > 0) cmbPort.SelectedIndex = 0; } catch (Exception ex) { AppendStatusLine("[刷新端口失败] " + ex.Message); } } // ===== 打开/关闭串口 ===== privatevoidToggleConnection() { if (_port.IsOpen) { ClosePort(); } else { OpenPort(); } } privatevoidOpenPort() { if (cmbPort.SelectedItem == null) { MessageBox.Show("请选择串口号。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } try { // 关闭自动发送 if (chkAutoSend.Checked) { chkAutoSend.Checked = false; } _port.PortName = cmbPort.SelectedItem.ToString()!; _port.BaudRate = Convert.ToInt32(cmbBaud.SelectedItem); _port.DataBits = Convert.ToInt32(cmbDataBits.SelectedItem); _port.Parity = ParseParity(cmbParity.SelectedItem.ToString()!); _port.StopBits = ParseStopBits(cmbStopBits.SelectedItem.ToString()!); _port.ReadTimeout = 500; _port.WriteTimeout = 500; _port.ReceivedBytesThreshold = 1; _port.Open(); UpdateConnectionState(); AppendStatusLine($"[已打开 {_port.PortName} @ {_port.BaudRate}]"); } catch (Exception ex) { MessageBox.Show("打开串口失败:\r\n" + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } } privatevoidClosePort() { try { if (chkAutoSend.Checked) chkAutoSend.Checked = false; _port.Close(); UpdateConnectionState(); AppendStatusLine("[串口已关闭]"); } catch (Exception ex) { MessageBox.Show("关闭串口失败:\r\n" + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } } privatevoidUpdateConnectionState() { if (_port.IsOpen) { btnOpenClose.Text = "关闭串口"; btnOpenClose.BackColor = Color.FromArgb(220, 53, 69); tslStatus.Text = " ● 已连接 "; tslStatus.ForeColor = Color.FromArgb(0, 150, 80); SetConfigEnabled(false); } else { btnOpenClose.Text = "打开串口"; btnOpenClose.BackColor = Color.FromArgb(0, 120, 215); tslStatus.Text = " ● 未连接 "; tslStatus.ForeColor = Color.Gray; SetConfigEnabled(true); } } privatevoidSetConfigEnabled(bool enabled) { cmbPort.Enabled = enabled; cmbBaud.Enabled = enabled; cmbDataBits.Enabled = enabled; cmbParity.Enabled = enabled; cmbStopBits.Enabled = enabled; btnRefresh.Enabled = enabled; } // ===== 发送 ===== privatevoidDoSend() { if (!_port.IsOpen) { if (chkAutoSend.Checked) chkAutoSend.Checked = false; return; } string text = txtSend.Text; if (string.IsNullOrEmpty(text)) return; byte[] data = chkTxHex.Checked ? HexStringToBytes(text) : Encoding.ASCII.GetBytes(text); if (data.Length == 0) return; try { if (chkSendNewLine.Checked && !chkTxHex.Checked) { byte[] withNl = new byte[data.Length + 2]; Array.Copy(data, 0, withNl, 0, data.Length); withNl[data.Length] = (byte)'\r'; withNl[data.Length + 1] = (byte)'\n'; data = withNl; } _port.Write(data, 0, data.Length); _txCount += data.Length; UpdateCounters(); } catch (Exception ex) { AppendStatusLine("[发送失败] " + ex.Message); if (chkAutoSend.Checked) chkAutoSend.Checked = false; } } // ===== 接收 (串口线程 -> UI 线程) ===== privatevoidOnDataReceived(object sender, SerialDataReceivedEventArgs e) { if (_port == null || !_port.IsOpen) return; try { int n = _port.BytesToRead; if (n <= 0) return; byte[] buf = new byte[n]; _port.Read(buf, 0, n); _rxCount += n; this.BeginInvoke((Action)(() => DisplayReceived(buf))); } catch (Exception ex) { this.BeginInvoke((Action)(() => AppendStatusLine("[接收失败] " + ex.Message))); } } privatevoidDisplayReceived(byte[] buf) { UpdateCounters(); string chunk; if (chkRxHex.Checked) { chunk = BytesToHexString(buf) + " "; _asciiBuf.Clear(); AppendReceiveText(chunk, Color.FromArgb(0, 100, 0)); } else { // ASCII / GBK 解码尝试 string s; try { s = Encoding.GetEncoding("GBK").GetString(buf); } catch { s = Encoding.ASCII.GetString(buf); } if (chkShowTime.Checked) { AppendReceiveText($"[{DateTime.Now:HH:mm:ss.fff}] ", Color.FromArgb(120, 120, 120)); } AppendReceiveText(s, Color.Black); } // 限制接收区长度, 避免内存膨胀 if (txtReceive.TextLength > 1_000_000) { txtReceive.Select(0, txtReceive.TextLength - 500_000); txtReceive.SelectedText = ""; } } privatevoidAppendReceiveText(string text, Color color) { txtReceive.SelectionStart = txtReceive.TextLength; txtReceive.SelectionLength = 0; txtReceive.SelectionColor = color; txtReceive.AppendText(text); txtReceive.SelectionColor = txtReceive.ForeColor; txtReceive.ScrollToCaret(); } privatevoidAppendStatusLine(string text) { AppendReceiveText(text + "\r\n", Color.FromArgb(180, 80, 0)); } // ===== 计数器 ===== privatevoidUpdateCounters() { tslRxCount.Text = $" RX: {_rxCount:N0} "; tslTxCount.Text = $" TX: {_txCount:N0} "; } // ===== 保存接收数据 ===== privatevoidSaveReceived() { if (txtReceive.TextLength == 0) { MessageBox.Show("接收区无数据可保存。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } using (var sfd = new SaveFileDialog()) { sfd.Filter = "文本文件 (*.txt)|*.txt|所有文件 (*.*)|*.*"; sfd.FileName = $"serial_rx_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; if (sfd.ShowDialog(this) == DialogResult.OK) { try { File.WriteAllText(sfd.FileName, txtReceive.Text, Encoding.UTF8); AppendStatusLine("[已保存到 " + sfd.FileName + "]"); } catch (Exception ex) { MessageBox.Show("保存失败:\r\n" + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } } // ===== 工具方法 ===== privatestatic Parity ParseParity(string s) { return s switch { "Odd" => Parity.Odd, "Even" => Parity.Even, "Mark" => Parity.Mark, "Space" => Parity.Space, _ => Parity.None, }; } privatestatic StopBits ParseStopBits(string s) { return s switch { "1.5" => StopBits.OnePointFive, "2" => StopBits.Two, _ => StopBits.One, }; } privatestaticstringBytesToHexString(byte[] buf) { var sb = new StringBuilder(buf.Length * 3); for (int i = 0; i < buf.Length; i++) { if (i > 0) sb.Append(' '); sb.Append(buf[i].ToString("X2")); } return sb.ToString(); } privatestaticbyte[] HexStringToBytes(string text) { // 移除空白、换行、0x 前缀等 string cleaned = text .Replace("0x", "") .Replace("0X", "") .Replace(" ", "") .Replace("\r", "") .Replace("\n", "") .Replace("\t", "") .Replace(",", ""); if ((cleaned.Length % 2) != 0) { cleaned = "0" + cleaned; } var bytes = new byte[cleaned.Length / 2]; for (int i = 0; i < bytes.Length; i++) { bytes[i] = Convert.ToByte(cleaned.Substring(i * 2, 2), 16); } return bytes; }