You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 07:40:04