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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 23:25:33