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

Mathematica读取大长度二进制串口流数据截断问题求助

Mathematica串口大长度二进制流读取截断问题

问题描述

在Mathematica中读取串口大长度(数千字节)二进制流时,存在可复现的数据截断问题。相同硬件与波特率下,Arduino串口监视器等工具可正常工作,该问题在最新14.3版本中仍存在。

测试环境

使用Arduino与Mathematica以115200波特率通信:

  • Mathematica发送ASCII格式整数n
  • Arduino回复n字节由小端序uint16_t连续值组成的二进制流,便于Mathematica端验证

测试结果

  • 当n < 4992时,可正常接收全部n字节
  • 当n ≥ 4992时,仅能接收前n-4992字节(例如n=4992时接收0字节,n=4994时仅接收2字节)
  • Mathematica端已配置大于请求长度的串口缓冲区(8192、16384字节),后续读取无法恢复丢失数据,且触发内部程序错误

测试代码

Arduino代码

#include <Arduino.h>

const unsigned long SERIAL_BAUD = 115200;

// Max ASCII input string length
const size_t  MAX_INPUT_LEN = 16;
uint16_t      i, n;

void waitForSerialNumber(uint32_t &byteCount) {
  char buf[MAX_INPUT_LEN + 1];
  size_t idx = 0;

  // 清空缓冲区
  memset(buf, 0, sizeof(buf));

  // 读取直到换行或缓冲区满
  while (true) {
    if (Serial.available() > 0) {
      char c = Serial.read();

      // 遇到换行或回车结束输入
      if (c == '\n' || c == '\r') {
        buf[idx] = '\0';
        break;
      }

      // 如果是数字且缓冲区有空间,添加到缓冲区
      if (idx < MAX_INPUT_LEN && isdigit((unsigned char)c)) {
        buf[idx++] = c;
      }

      // 缓冲区满时结束输入
      if (idx >= MAX_INPUT_LEN) {
        buf[idx] = '\0';
        break;
      }
    }
  }

  // 字符串转无符号长整数
  unsigned long n = strtoul(buf, NULL, 10);

  // 限制最大值(uint16_t*2范围最大65534)
  if (n > 65534UL) {
    n = 65534UL;
  }

  byteCount = (uint32_t)n;
}

void setup() {
  Serial.begin(SERIAL_BAUD);
  while (!Serial) {
    ; // 等待串口打开
  }

  // 测试用欢迎消息
  Serial.println("MKZero ready. Send number (bytes to transmit) followed by newline.");
}

void loop() {
  uint32_t totalBytesToSend = 0;

  // 等待串口数据
  if (Serial.available() > 0) {
    // 1) 读取串口的ASCII数字并转为无符号长整数
    waitForSerialNumber(totalBytesToSend);

    // 2) 发送恰好totalBytesToSend字节的小端序递增整数流
    //    注意:如果totalBytesToSend是奇数,最后一个uint16值会被部分发送

    i = 0; n = 0;
    while (true) {
      // 小端序:低字节在前
      uint8_t lo = (uint8_t)(i & 0xFF);
      uint8_t hi = (uint8_t)((i++ >> 8) & 0xFF);

      Serial.write(lo); n++; if(n>=totalBytesToSend) break;
      Serial.write(hi); n++; if(n>=totalBytesToSend) break;
    }

  }
}

Mathematica代码

In[385]:= port = DeviceOpen[
  "Serial", {"/dev/cu.usbmodem211401" , "BaudRate" -> 115200 , 
   "ReadBufferSize" -> 16384}]
(* 测试完成后执行DeviceClose[port]关闭串口 *)

Out[385]= DeviceObject[{"Serial", 1}]

(* 自定义函数,将列表转换为类似Short[]的字符串格式,方便粘贴到网页 *)

shortString[lista_, n_ : 4] := 
 With[{len = Length[lista]}, 
  If[len <= 2 n, ToString[lista, InputForm], 
   StringJoin["{", Riffle[ToString[#, InputForm] & /@ Take[lista, n], ", "], 
    ", <<", ToString[len - 2 n], ">>, ", 
    Riffle[ToString[#, InputForm] & /@ Take[lista, -n], ", "], "}"]]

In[435]:= Clear[serialtest]
serialtest[streamlength_, OptionsPattern["grid" -> True]] := 
 Module[{buf, buflen, firstbufval, lastbufval, lastexpected},
  (* 发送ASCII格式的字节流请求 *)
  
  DeviceWrite[port, ToString[streamlength] <> "\n"];
  (* 等待一段时间再开始读取串口 *)
  Pause[.5];
  (* 读取串口缓冲区并去除连续的0xFF *)
  
  buf = SequenceReplace[DeviceReadBuffer[port], {255, 255} -> 255];
  (* 缓冲区应包含streamlength字节数据 *)
  buflen = Length[buf];
  (* 检查接收到的第一个和最后一个完整的uint16值 *)
  
  firstbufval = If[Length[buf] >= 2, buf[[{1, 2}]] /. {l_, h_} :> 256 h + l];
  lastbufval = 
   If[Length[buf] >= 2, 
    Take[buf, Floor[Length[buf], 2]][[{-2, -1}]] /. {l_, h_} :> 256 h + l];
  lastexpected = Floor[streamlength, 2]/2 - 1;
  
  If[OptionValue["grid"],
   Quiet@StringRiffle[
     {"接收到的字节流: " <> shortString[buf],
      "字节流长度: " <> ToString[buflen],
      "第一个接收到的uint16值: " <> ToString[firstbufval],
      "最后一个完整的uint16值: " <> ToString[lastbufval],
      "预期的最后一个完整uint16值: " <> ToString[lastexpected]
      }, "\n"]
   ,
   <|"请求长度" -> streamlength, "实际接收长度" -> buflen, "第一个值" -> firstbufval, 
    "最后一个完整值" -> lastbufval, "预期最后值" -> lastexpected|>
   ]
  ]

In[446]:= serialtest[4990]

Out[446]= "接收到的字节流: {0, 0, 1, 0, <<4982>>, 189, 9, 190, 9}
字节流长度: 4990
第一个接收到的uint16值: 0
最后一个完整的uint16值: 2494
预期的最后一个完整uint16值: 2494"

从n=4992开始执行会触发错误,此处保留代码仅为提高可读性

In[447]:= serialtest[4992]

During evaluation of In[447]:= MyFunction::info: -- 未找到消息文本 -- (从库函数内调用消息)

Out[447]= "接收到的字节流: {}
字节流长度: 0
第一个接收到的uint16值: Null
最后一个完整的uint16值: Null
预期的最后一个完整uint16值: 2495"

In[448]:= serialtest[4998]

Out[448]= "接收到的字节流: {0, 0, 1, 0, 2, 0}
字节流长度: 6
第一个接收到的uint16值: 0
最后一个完整的uint16值: 2
预期的最后一个完整uint16值: 2498"

In[469]:= testresults = Table[serialtest[i, "grid" -> False], {i, 4985, 5000}]

Out[469]= {<|"请求长度" -> 4985, "实际接收长度" -> 4985, "第一个值" -> 0, "最后一个完整值" -> 2491, 
  "预期最后值" -> 2491|>, <|"请求长度" -> 4986, "实际接收长度" -> 4986, "第一个值" -> 0,
   "最后一个完整值" -> 2492, "预期最后值" -> 2492|>, <|"请求长度" -> 4987, 
  "实际接收长度" -> 4987, "第一个值" -> 0, "最后一个完整值" -> 2492, 
  "预期最后值" -> 2492|>, <|"请求长度" -> 4988, "实际接收长度" -> 4988, "第一个值" -> 0,
   "最后一个完整值" -> 2493, "预期最后值" -> 2493|>, <|"请求长度" -> 4989, 
  "实际接收长度" -> 4989, "第一个值" -> 0, "最后一个完整值" -> 2493, 
  "预期最后值" -> 2493|>, <|"请求长度" -> 4990, "实际接收长度" -> 4990, "第一个值" -> 0,
   "最后一个完整值" -> 2494, "预期最后值" -> 2494|>, <|"请求长度" -> 4991, 
  "实际接收长度" -> 4991, "第一个值" -> 0, "最后一个完整值" -> 2494, 
  "预期最后值" -> 2494|>, <|"请求长度" -> 4992, "实际接收长度" -> 0, "第一个值" -> Null,
   "最后一个完整值" -> Null, "预期最后值" -> 2495|>, <|"请求长度" -> 4993, "实际接收长度" -> 1,
   "第一个值" -> Null, "最后一个完整值" -> Null, 
  "预期最后值" -> 2495|>, <|"请求长度" -> 4994, "实际接收长度" -> 2, "第一个值" -> 0, 
  "最后一个完整值" -> 0, "预期最后值" -> 2496|>, <|"请求长度" -> 4995, "实际接收长度" -> 3, 
  "第一个值" -> 0, "最后一个完整值" -> 0, "预期最后值" -> 2496|>, <|"请求长度" -> 4996, 
  "实际接收长度" -> 4, "第一个值" -> 0, "最后一个完整值" -> 1, 
  "预期最后值" -> 2497|>, <|"请求长度" -> 4997, "实际接收长度" -> 5, "第一个值" -> 0, 
  "最后一个完整值" -> 1, "预期最后值" -> 2497|>, <|"请求长度" -> 4998, "实际接收长度" -> 6, 
  "第一个值" -> 0, "最后一个完整值" -> 2, "预期最后值" -> 2498|>, <|"请求长度" -> 4999, 
  "实际接收长度" -> 7, "第一个值" -> 0, "最后一个完整值" -> 2, 
  "预期最后值" -> 2498|>, <|"请求长度" -> 5000, "实际接收长度" -> 8, "第一个值" -> 0, 
  "最后一个完整值" -> 3, "预期最后值" -> 2499|>}

求助

寻求是否有类似问题经验,或是否存在读取方式错误的指正。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.11 15:35:55