Arduino与LoRa图片传输异常排查及可靠传输方案咨询
Arduino LoRa图片传输问题排查与解决方案
一、代码错误排查
1. 二进制转字符串丢失数据
图片是二进制文件,包含大量\0(空字节)。发送端将读取的二进制数组转成String时,\0会被当作字符串终止符,导致后续字节被截断;同时手动添加cArray[length] = '\0'会越界(若cArray仅定义为32字节),还会额外写入无效字节。
2. 接收端文件覆盖写入
接收端每次收到数据包都用FILE_WRITE打开文件,默认是覆盖模式,而非追加模式,导致只有最后一包内容被保留,前面的数据包全部被覆盖,这是文件变小的核心原因。
3. 长度传递错误
发送端通过cArray.length()获取长度,但因字符串转换丢失数据,该长度已不等于实际读取的字节数,应该直接传递读取的原始长度值。
修正后的关键代码
发送端
// 直接发送二进制数组,避免String转换 void sendMessage(byte* data, int length) { LoRa.beginPacket(); LoRa.write(length); // 传递实际读取的字节数 LoRa.write(data, length); // 按指定长度发送二进制数据 LoRa.endPacket(); } // 主发送逻辑 while (cByteTotal > 0) { int length = cByteTotal < 32 ? cByteTotal : 32; if (length < 32) { Serial.println("发送最后一包"); } // 读取数据并校验读取结果 int readBytes = oFile.read(cArray, length); if (readBytes != length) { Serial.println("文件读取失败"); break; } sendMessage(cArray, length); delay(250); cByteTotal -= length; } oFile.close();
接收端
File cFile; bool fileOpened = false; void onReceive(int packetSize) { if (packetSize == 0) return; packageTotal++; byte incomingLength = LoRa.read(); byte incomingData[32]; // 读取指定长度的二进制数据 int readCount = LoRa.readBytes(incomingData, incomingLength); if (readCount != incomingLength) { Serial.println("数据包长度错误"); return; } // 首次接收时以追加模式打开文件,避免重复打开关闭 if (!fileOpened) { cFile = SD.open("wimage.jpg", FILE_WRITE | O_APPEND); if (!cFile) { Serial.println("无法打开目标文件"); return; } fileOpened = true; } // 写入二进制数据 cFile.write(incomingData, incomingLength); } // 所有包接收完成后需手动关闭文件,可通过结束标记包触发
二、带确认的错误校验实现(停等协议)
采用停等机制,每发送一包后等待接收端的确认信号,校验通过再发下一包,确保数据完整性。
1. 数据包格式
每包包含4部分:
- 1字节:包序号(0~255循环使用)
- 1字节:数据长度(1~32)
- N字节:二进制数据内容
- 1字节:校验和(包序号+长度+所有数据字节的异或值)
2. 确认包格式
- 1字节:确认标记(
0x01表示校验通过,0x00表示失败) - 1字节:确认对应的包序号
核心实现代码片段
发送端
byte packetIndex = 0; bool waitAck = false; void loop() { // 监听接收端的确认包 if (LoRa.parsePacket() > 0) { byte ackFlag = LoRa.read(); byte ackIndex = LoRa.read(); if (ackFlag == 0x01 && ackIndex == packetIndex) { waitAck = false; packetIndex++; } else if (ackFlag == 0x00 && ackIndex == packetIndex) { waitAck = false; // 准备重发当前包 } } // 未等待确认时发送下一包 if (!waitAck && cByteTotal > 0) { int length = cByteTotal < 32 ? cByteTotal : 32; int readBytes = oFile.read(cArray, length); if (readBytes != length) break; // 计算异或校验和 byte checksum = packetIndex ^ length; for (int i = 0; i < length; i++) { checksum ^= cArray[i]; } // 发送完整数据包 LoRa.beginPacket(); LoRa.write(packetIndex); LoRa.write(length); LoRa.write(cArray, length); LoRa.write(checksum); LoRa.endPacket(); waitAck = true; unsigned long startTime = millis(); // 超时重发逻辑(1秒超时) while (waitAck && millis() - startTime < 1000) { if (LoRa.parsePacket() > 0) { byte ackFlag = LoRa.read(); byte ackIndex = LoRa.read(); if (ackFlag == 0x01 && ackIndex == packetIndex) { waitAck = false; packetIndex++; cByteTotal -= length; } else if (ackFlag == 0x00 && ackIndex == packetIndex) { waitAck = false; } } } // 超时未收到确认,重发当前包 if (waitAck) { waitAck = false; } } }
接收端
byte expectedIndex = 0; File cFile; bool fileOpened = false; void loop() { int packetSize = LoRa.parsePacket(); if (packetSize > 0) { byte pktIndex = LoRa.read(); byte dataLength = LoRa.read(); byte data[32]; int readCount = LoRa.readBytes(data, dataLength); byte receivedChecksum = LoRa.read(); // 计算校验和 byte checksum = pktIndex ^ dataLength; for (int i = 0; i < dataLength; i++) { checksum ^= data[i]; } byte ackFlag = 0x00; // 校验通过且包序号符合预期 if (readCount == dataLength && checksum == receivedChecksum && pktIndex == expectedIndex) { if (!fileOpened) { cFile = SD.open("wimage.jpg", FILE_WRITE | O_APPEND); if (!cFile) { Serial.println("文件打开失败"); } else { fileOpened = true; cFile.write(data, dataLength); ackFlag = 0x01; expectedIndex++; } } else { cFile.write(data, dataLength); ackFlag = 0x01; expectedIndex++; } } // 发送确认包 LoRa.beginPacket(); LoRa.write(ackFlag); LoRa.write(pktIndex); LoRa.endPacket(); } }
内容的提问来源于stack exchange,提问作者acanessa
相关产品推荐
相关产品推荐

