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

Arduino IDE串口数据更新异常:update函数未触发问题排查

Arduino串口接收函数未触发问题排查与修复

问题描述

编写了一款持续输出数字的Arduino程序,预期串口接收数据时调用update()函数处理字符串并将结果存入数组,但程序始终未正确触发该函数的处理逻辑,请求排查原因。

原代码

const byte buffSize = 60;
char inputBuffer[buffSize];
const char startMarker = '<';
const char endMarker = '>';
byte bytesRecvd = 0;
boolean readInProgress = false;
boolean newDataFromPC = false;
char messageFromPC[buffSize] = {0};
uint32_t tmr1;         // 计时器变量
int time = 0;
int endtest = 0;
int starttest = 0;

void setup() {
  Serial.begin(115200);
  delay(500);
}

void loop() {

  if(Serial.available() > 0){
    int *workmas = update();
    starttest = workmas[0];
    time = workmas[1];
    endtest = workmas[2];
  }

  int n = starttest;
  if (millis() - tmr1 >= time && time > 0) {  // 计算时间差
    tmr1 = millis();                   // 重置计时器
    int count = 0;
    while(n < endtest){
        n = n + 1;
        count = count + 1;
        delay(250);
        Serial.print("< ");
        Serial.print(n);
        Serial.print(" ");
        Serial.print(count);
        Serial.println(" >");
    }
  }

  Serial.print("<");
  Serial.print(" 1 ");
  Serial.println(">");
  delay(200);
}

int *update(){
  int arrmas[3];

  char x = Serial.read();
  
  if (x == endMarker) {
      
    readInProgress = false;
    newDataFromPC = true;
    inputBuffer[bytesRecvd] = 0;
    char * strtokIndx; // strtok()使用的索引
  
    strtokIndx = strtok(inputBuffer,",");      // 获取第一部分
    arrmas[0] = atoi(strtokIndx); // 转换为整数

    strtokIndx = strtok(NULL, ","); // 继续分割
    arrmas[1] = atoi(strtokIndx);     // 转换为整数

    strtokIndx = strtok(NULL, ","); // 继续分割
    arrmas[2] = atoi(strtokIndx);     // 转换为整数

  }
  if(readInProgress) {
      inputBuffer[bytesRecvd] = x;
      bytesRecvd ++;
      if (bytesRecvd == buffSize) {
        bytesRecvd = buffSize - 1;
      }
    }

    if (x == startMarker) { 
      bytesRecvd = 0; 
      readInProgress = true;
    }

  return arrmas;
}

核心问题分析

1. 局部数组返回导致的野指针问题

update()函数中定义的arrmas[3]是局部变量,函数执行完毕后,栈上的该数组会被销毁,返回的指针指向已释放的内存,属于野指针。后续访问workmas[0]、workmas[1]等会触发未定义行为,数据完全不可靠。

2. 串口接收逻辑顺序颠倒

update()中先判断结束标记endMarker,再处理中间字符存储,最后判断起始标记startMarker,这会导致:

  • 起始标记被当作普通字符存储
  • 结束标记的处理逻辑无法正确触发完整数据的解析

3. 串口数据处理不完整

loop()中仅在Serial.available()>0时调用一次update(),但串口数据可能分批到达,单次调用只能读取一个字节,无法完成多字节数据的拼接与解析。

4. 数据更新逻辑错误

代码定义了newDataFromPC标记,但未用它判断是否完成完整数据接收,而是每次读取字节就强制更新全局变量,导致未完成解析的数据被错误赋值。

修复后的代码

const byte buffSize = 60;
char inputBuffer[buffSize];
const char startMarker = '<';
const char endMarker = '>';
byte bytesRecvd = 0;
boolean readInProgress = false;
boolean newDataFromPC = false;
uint32_t tmr1;         
int time = 0;
int endtest = 0;
int starttest = 0;
// 用全局数组替代局部数组,避免野指针问题
int arrmas[3] = {0};

void setup() {
  Serial.begin(115200);
  delay(500);
}

void loop() {
  // 循环处理所有可用的串口字节,确保数据不丢失
  while(Serial.available() > 0){
    update();
  }

  // 只有当完整接收完一组数据后,才更新全局变量
  if(newDataFromPC){
    starttest = arrmas[0];
    time = arrmas[1];
    endtest = arrmas[2];
    newDataFromPC = false; // 重置标记
  }

  int n = starttest;
  if (millis() - tmr1 >= time && time > 0) {  
    tmr1 = millis();                   
    int count = 0;
    while(n < endtest){
        n = n + 1;
        count = count + 1;
        delay(250);
        Serial.print("< ");
        Serial.print(n);
        Serial.print(" ");
        Serial.print(count);
        Serial.println(" >");
    }
  }

  Serial.print("<");
  Serial.print(" 1 ");
  Serial.println(">");
  delay(200);
}

void update(){
  char x = Serial.read();
  
  // 先判断起始标记,初始化接收状态
  if (x == startMarker) { 
    bytesRecvd = 0; 
    readInProgress = true;
    return;
  }

  // 再处理中间接收的字符
  if(readInProgress) {
      inputBuffer[bytesRecvd] = x;
      bytesRecvd ++;
      if (bytesRecvd >= buffSize - 1) { // 留一个位置存终止符
        bytesRecvd = buffSize - 1;
      }
  }

  // 最后判断结束标记,完成数据解析
  if (x == endMarker && readInProgress) {
    readInProgress = false;
    newDataFromPC = true;
    inputBuffer[bytesRecvd] = '\0'; // 手动添加字符串终止符
    char * strtokIndx;
  
    strtokIndx = strtok(inputBuffer,",");      
    arrmas[0] = strtokIndx ? atoi(strtokIndx) : 0; // 增加空指针判断,避免崩溃

    strtokIndx = strtok(NULL, ","); 
    arrmas[1] = strtokIndx ? atoi(strtokIndx) : 0;

    strtokIndx = strtok(NULL, ","); 
    arrmas[2] = strtokIndx ? atoi(strtokIndx) : 0;
  }
}

关键修改点

  • 将arrmas改为全局数组,彻底解决野指针问题
  • 调整update()的接收逻辑顺序:先处理起始标记,再存储中间字符,最后处理结束标记
  • 在loop()中用while循环处理所有串口可用字节,确保数据完整接收
  • 仅当newDataFromPC标记为true时才更新全局变量,保证数据是完整解析后的结果
  • 增加strtok返回空指针的判断,避免非法访问导致崩溃

内容的提问来源于stack exchange,提问作者Андрей Шарапов

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 08:57:37