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
相关产品推荐
相关产品推荐

