C#串口读取多端口秤数据求助:数据不完整且需格式化显示
串口读取称重秤数据问题
我是C#新手,现需通过串口读取连接在8端口控制器上的8台称重秤数据,目前正在测试前2个端口。读取到的数据有时是完整的S 0.006kg,有时仅为S、空值或换行符,希望最终能在文本框中显示0.003这类格式的数值。以下是我编写的代码:
namespace SimpleSerialLogic { public partial class Form1 : Form { public string Selected_Port_Baudrate; public bool test1; public int count, portno; public byte[] data; public string Rx1, Rx2, Rx3, Rx4, Rx5, Rx6, Rx7, Rx8; public string response1, response2; public SerialPort COMport; public Form1() { InitializeComponent(); } public void Form1_Load(object sender, EventArgs e) { test1 = true; portno = 1; PortScanLogic(); } public async void PortScanLogic() { COMport = new SerialPort("COM4", 9600, Parity.None, 8, StopBits.One); COMport.ReadTimeout = 500; COMport.WriteTimeout = 500; try { COMport.Open(); } catch //Any other ERROR { MessageBox.Show("Cannot Open Port First scan"); } if (portno > 8) { portno = 0; } if (portno < 0) { portno = 0; } if (COMport.IsOpen == true) { if (portno == 1) { textBox9.Text = portno.ToString(); COMport.Write(Convert.ToString('\u0014') + "1"); COMport.Write("SI" + Convert.ToString('\r') + Convert.ToString('\n')); Port1ScanLogic(); } if (portno == 2) { textBox9.Text = portno.ToString(); COMport.Write(Convert.ToString('\u0014') + "2"); COMport.Write("SI" + Convert.ToString('\r') + Convert.ToString('\n')); Port2ScanLogic(); } await Task.Delay(1000); PortScanLogic(); } else { MessageBox.Show("Unable to Open the Port "); } } public async void Port1ScanLogic() { var bytesToRead = COMport.BytesToRead; byte[] Data1 = new Byte[bytesToRead]; int received = COMport.Read(Data1, 0, bytesToRead); response1 = System.Text.Encoding.UTF8.GetString(Data1); textBox1.Text = response1; COMport.Dispose(); await Task.Delay(500); portno = 2; PortScanLogic(); } public async void Port2ScanLogic() { var bytesToRead = COMport.BytesToRead; byte[] Data2 = new Byte[bytesToRead]; int received = COMport.Read(Data2, 0, bytesToRead); response2 = System.Text.Encoding.UTF8.GetString(Data2); textBox2.Text = response2; COMport.Dispose(); await Task.Delay(500); portno = 1; PortScanLogic(); } private void button2_Click(object sender, EventArgs e) { this.Close(); } public void Form1_FormClosing(object sender, FormClosingEventArgs e) { COMport.Close(); } } }
问题分析与修复方案
核心问题
- 串口实例频繁创建销毁:每次调用
PortScanLogic都新建SerialPort,还在端口读取方法中调用Dispose,导致串口连接不稳定,数据接收不完整。 - 同步读取时机错误:发送指令后立刻读取,设备还未返回完整数据,只能读到部分内容。
- 无数据缓存拼接机制:串口数据分批次到达,未缓存拼接就直接处理,导致截断。
- 缺少数据解析逻辑:未对完整响应进行数值提取和格式化,直接显示原始内容。
修复后的完整代码
namespace SimpleSerialLogic { public partial class Form1 : Form { private SerialPort _comPort; private int _currentPortNo = 1; // 为每个端口缓存接收的数据(索引1-8对应端口1-8) private readonly string[] _portDataBuffers = new string[9]; public Form1() { InitializeComponent(); InitializeSerialPort(); } private void InitializeSerialPort() { _comPort = new SerialPort("COM4", 9600, Parity.None, 8, StopBits.One) { ReadTimeout = 500, WriteTimeout = 500, NewLine = "\r\n" // 匹配设备响应的换行符,用于识别完整数据 }; _comPort.DataReceived += ComPort_DataReceived; try { _comPort.Open(); } catch { MessageBox.Show("无法打开串口"); } } private void Form1_Load(object sender, EventArgs e) { // 启动端口轮询任务 Task.Run(async () => await PollPortsAsync()); } private async Task PollPortsAsync() { while (_comPort.IsOpen) { // 限定端口范围,当前测试前2个端口 _currentPortNo = _currentPortNo switch { <1 => 1, >2 => 1, _ => _currentPortNo }; textBox9.Text = _currentPortNo.ToString(); // 发送端口切换指令+读取指令 _comPort.Write($"\u0014{_currentPortNo}"); _comPort.WriteLine("SI"); // 等待设备返回数据,可根据实际设备响应速度调整延迟 await Task.Delay(200); // 切换到下一个端口 _currentPortNo = _currentPortNo == 1 ? 2 : 1; await Task.Delay(800); } } private void ComPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { if (!_comPort.IsOpen) return; try { // 读取当前串口缓冲区的所有数据 string receivedData = _comPort.ReadExisting(); // 将数据追加到当前端口的缓存中 _portDataBuffers[_currentPortNo] += receivedData; // 判断是否收到完整响应(以设备指定的换行符为结束标志) if (_portDataBuffers[_currentPortNo].Contains(_comPort.NewLine)) { string fullResponse = _portDataBuffers[_currentPortNo].Trim(); // 清空当前端口缓存,准备下一次接收 _portDataBuffers[_currentPortNo] = string.Empty; // 解析并格式化数据 string formattedValue = ParseAndFormatWeight(fullResponse); // 跨线程更新对应文本框 UpdatePortTextBox(_currentPortNo, formattedValue); } } catch (Exception ex) { MessageBox.Show($"数据接收出错:{ex.Message}"); } } private string ParseAndFormatWeight(string rawData) { // 原始数据示例:"S 0.006kg",提取数字部分并格式化 var parts = rawData.Split(new[] { ' ', 'k' }, StringSplitOptions.RemoveEmptyEntries); if (parts.Length >= 2 && decimal.TryParse(parts[1], out decimal weight)) { // 格式化为保留三位小数的字符串 return weight.ToString("F3"); } // 解析失败返回空字符串 return string.Empty; } private void UpdatePortTextBox(int portNo, string value) { // 跨线程更新UI,必须使用Invoke确保线程安全 if (InvokeRequired) { Invoke(new Action<int, string>(UpdatePortTextBox), portNo, value); return; } switch (portNo) { case 1: textBox1.Text = value; break; case 2: textBox2.Text = value; break; // 后续扩展8个端口时,继续添加对应的case } } private void button2_Click(object sender, EventArgs e) { Close(); } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { if (_comPort != null && _comPort.IsOpen) { _comPort.DataReceived -= ComPort_DataReceived; _comPort.Close(); _comPort.Dispose(); } } } }
关键改进说明
- 复用串口实例:仅在Form初始化时创建一次
SerialPort,避免频繁开关串口的不稳定问题。 - 异步事件接收数据:使用
DataReceived事件处理数据,确保数据到达时再读取,避免同步读取的时机误差。 - 端口数据缓存:为每个端口单独缓存接收数据,直到识别到完整响应后再处理,解决数据截断问题。
- 数据解析格式化:从原始响应中提取数值,格式化为三位小数的目标格式。
- 线程安全UI更新:通过
Invoke实现跨线程更新文本框,避免WinForm的线程安全异常。
内容的提问来源于stack exchange,提问作者Thiru
相关产品推荐
相关产品推荐

