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

C#串口读取Arduino连续数据问题:每分钟读取时数据错位缺失

AGV Arduino与C#串口通信问题解决方案

问题根源

你用Timer定时读取4行数据的方式本质上是「主动轮询」,但Timer的触发时机和Arduino的发送节奏完全不匹配——Arduino连续发送数据时,Timer触发可能刚好卡在一组数据的中间,导致读取的4行不属于同一组,最终数据对应错误。正确的做法是用串口的DataReceived事件被动接收数据,再缓存处理完整的分组。

新手友好解决方案步骤

1. 准备全局变量

在Form类中定义全局变量,用于缓存未处理的数据和完整行:

private SerialPort sp;
private string receivedBuffer = string.Empty; // 缓存所有未处理的串口数据
private List<string> dataLines = new List<string>(); // 存储拆分后的完整行

2. 替换Timer为DataReceived事件

在打开串口的代码中(比如按钮点击事件),绑定DataReceived事件,不再启动Timer:

private void btnOpenPort_Click(object sender, EventArgs e)
{
    sp = new SerialPort("COM3", 9600); // 替换为你的串口名和波特率
    sp.DataReceived += Sp_DataReceived; // 绑定数据接收事件
    sp.Open();
    // 注释掉原来的timer1.Start();
}

3. 实现DataReceived事件处理函数

这个函数会在串口有数据到达时自动触发,负责缓存、拆分完整行、分组处理:

private void Sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    // 读取串口缓冲区所有数据
    string newData = sp.ReadExisting();
    // 追加到缓存
    receivedBuffer += newData;

    // 拆分缓存为行(兼容Arduino的换行符)
    string[] lines = receivedBuffer.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
    
    // 处理不完整的行:如果缓存末尾不是换行符,说明最后一行不完整,放回缓存
    if (receivedBuffer.EndsWith(Environment.NewLine))
    {
        receivedBuffer = string.Empty;
        dataLines.AddRange(lines);
    }
    else
    {
        // 只保留完整行,不完整的行放回缓存
        for (int i = 0; i < lines.Length - 1; i++)
        {
            dataLines.Add(lines[i]);
        }
        receivedBuffer = lines[lines.Length - 1];
    }

    // 凑够4行一组就处理
    while (dataLines.Count >= 4)
    {
        // 取出一组4行数据
        string s1 = dataLines[0];
        string s2 = dataLines[1];
        string s3 = dataLines[2];
        string s4 = dataLines[3];
        // 移除已处理的行,避免重复解析
        dataLines.RemoveRange(0, 4);

        // 跨线程更新UI必须用Invoke(DataReceived在后台线程运行)
        this.Invoke(new Action(() =>
        {
            try
            {
                string[] result_batt = s1.Split(',');
                string[] result_prox = s2.Split(',');
                string[] result_RPM = s3.Split(',');
                string[] result_speed = s4.Split(',');

                // 增加数组长度检查,避免索引越界
                if (result_batt.Length >=4) get_Batt(result_batt[0], result_batt[1], result_batt[2], result_batt[3]);
                if (result_prox.Length >=4) get_prox(result_prox[0], result_prox[1], result_prox[2], result_prox[3]);
                if (result_RPM.Length >=2) get_RPM(result_RPM[0], result_RPM[1]);
                if (result_speed.Length >=2) get_speed(result_speed[0], result_speed[1]);
            }
            catch (Exception ex)
            {
                // 错误提示,避免程序崩溃
                labelError.Text = $"解析错误:{ex.Message}";
            }
        }));
    }
}

4. 优化原有处理函数(增加稳定性)

电池信息处理(避免转换错误)

private void get_Batt(string s0, string s1, string s2, string s3)
{
    string num = Regex.Replace(s1, "[%]", "");
    if (int.TryParse(num, out int batt_val))
    {
        // 确保进度条值在合法范围内
        progressBar1.Value = Math.Clamp(batt_val, 0, progressBar1.Maximum);
        if (batt_val <= 0)
        {
            label2.Text = "Working time remaining: Unknown";
            label3.Text = batt_val + "%";
        }
        else
        {
            label2.Text = "Working time remaining: xx:xx:xx";
            label3.Text = batt_val + "%";
        }
    }
}

接近传感器处理(去除空格干扰)

private void get_prox(string s0, string s1, string s2, string s3)
{
    // 去除字符串前后空格,避免Arduino发送带空格的数据导致判断错误
    s0 = s0.Trim();
    s1 = s1.Trim();
    s2 = s2.Trim();
    s3 = s3.Trim();
    
    label26.Text = s3;
    
    // 用else if避免重复判断
    if (s3 == "0")
    {
        blackCircle4.Show();
        greenCircle8.Hide();
    }
    else if (s3 == "1")
    {
        blackCircle8.Show();
        greenCircle4.Hide();
    }
    
    if (s0 == "0")
    {
        blackCircle1.Show();
        greenCircle5.Hide();
    }
    else if (s0 == "1")
    {
        blackCircle5.Show();
        greenCircle1.Hide();
    }
    
    if (s1 == "0")
    {
        blackCircle2.Show();
        greenCircle6.Hide();
    }
    else if (s1 == "1")
    {
        blackCircle6.Show();
        greenCircle2.Hide();
    }
    
    if (s2 == "0")
    {
        blackCircle3.Show();
        greenCircle7.Hide();
    }
    else if (s2 == "1")
    {
        blackCircle7.Show();
        greenCircle3.Hide();
    }
}

5. 清理Timer相关代码

删除原有的timer1_Tick函数,移除Form中的Timer控件即可。

关键注意事项

  1. 线程安全:DataReceived事件在后台线程运行,必须用this.Invoke更新UI,否则会触发跨线程错误。
  2. 参数匹配:C#串口的波特率、数据位、停止位必须和Arduino代码完全一致(比如Arduino用Serial.begin(9600);,C#也要设9600)。
  3. Arduino端优化(可选):可以给每组数据增加起始/结束标记,比如发送START开头、END结尾,C#端可以先匹配标记再解析分组,进一步避免错位:
    Serial.println("START");
    Serial.println("00.0V, 00.0%, 000, CL");
    Serial.println("1,1,1,1");
    Serial.println("140,140");
    Serial.println("5,5");
    Serial.println("END");
    

内容的提问来源于stack exchange,提问作者ZHLN

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 02:45:35