设计一个文件传输的协议-Arduino中文社区 - Powered by Discuz! Archiver

Zoologist 发表于 2017-8-3 21:58

设计一个文件传输的协议

很多时候,我们需要从串口无损传输一个文件。但是对于串口来说,经常会发生丢失数据或者是数据错误的情况。因此,需要有一个协议来保证传输的正确性。这里设计一个简单的协议。首先,打开接收端等待传输,发送端传输一个4字节的文件长度。之后的数据都是以N为单位(比如:16字节或者128字节等等)进行发送。数据包的长度为 N字节。其中有2个字节是用于校验。一个是 Checksum: 将0到N-2加到一起,要等于0;另一个是Index取值从0-255,比如一个包是0,第二个发送过来的就是1,这样一直下去,到255后再返回到0。在传输阶段,接收端用“N”来表示请发送下一个数据包,接收端对收到的数据进行校验,如果出现错误,发送“R”表示要求发送端重新发送。设计完成就是代码的实现。我编写了一对Windows 程序用来发送和接受,使用的是 128字节的数据包;此外就是一个Windows发送端,还有接受的Arduino代码。在Windows下,推荐使用Virtual Serial port Driver 这个软件调试。他能够虚拟出来连通的串口,这样无需在外部的Loopback 直接就可以调用串口非常方便。我传输了一个200K的内容,之后对比过,发送和接受内容是相同的。我将代码直接打包,有兴趣的朋友可以研究一下(Delphi XE2编译)。 这是上位机的发送部分,每次收到N或者R后会根据情况进行发送procedure TForm2.ComPort1RxChar(Sender: TObject; Count: Integer);
var
s,t: String;

aSize,i:integer;
checksum:byte;
begin
ComPort1.ReadStr(s, Count);
if s='N' then
    begin
      Memo1.Lines.Add('Received N');
      if BytesSend<stream.size then
         begin
         fillchar(Buffer,BUFFERSIZE,0);
         // 读取文件并取得实际读取出来的长度
         aSize:=stream.Read(Buffer,BUFFERSIZE-2);
         //计算Checksum, 就是 Buffer 第一个到倒数第二个加起来要求为0
         checksum:=0;
         for i := 0 to BUFFERSIZE-3 do
             begin
               checksum:=checksum-Buffer;
             end;
         //放置Checksum
         Buffer:=checksum;
         //放置顺序号
         Buffer:=Index;
         inc(Index);
         ComPort1.Write(Buffer,BUFFERSIZE);
         {t:='';
         for i := 0 to BUFFERSIZE-1 do
             begin
               t:=t+ IntToHex(Buffer,2)+' ';
             end;
            Memo1.Lines.Add(t);
         }
         BytesSend:=BytesSend+aSize;
         Form2.Caption:=IntToStr(BytesSend)+'/'+IntToStr(stream.size);
         Form2.Refresh;
         end
      else
         Memo1.Lines.Add('Completed!');
    end;
//如果收到 R 就再次发送
if s='R' then
    begin
      ComPort1.Write(Buffer,BUFFERSIZE);
      {   t:='';
         for i := 0 to BUFFERSIZE-1 do
             begin
               t:=t+ IntToHex(Buffer,2)+' ';
             end;
            Memo1.Lines.Add(t);
      }
      Memo1.Lines.Add('R');
    end;

end;
Arduino代码如下 (Arduino我没有进行Index的校验)#include <SoftwareSerial.h>

#define BUFFERSIZE 16
#define TIMEOUT 3000UL
#define DEBUG (1)

//用来 DEBUG 的软串口,用额外的USB转串口线接在Pin11即可
SoftwareSerial mySerial(10, 11); // RX, TX

char buffer;

unsigned long filesize=0;//文件大小
byte *p=(byte *)&filesize;
unsigned long total=0;

void setup() {
//收到的文件大小字节数,一共4位
byte c=0;
//每次收到的数值
byte r;

Serial.begin(115200);

if DEBUG {
      // set the data rate for the SoftwareSerial port
      mySerial.begin(115200);
      mySerial.println("Hello, world?");
}   

//如果没有收够4字节,则一直接收
while (c<4) {
    while (Serial.available() > 0)
      {
      //比如文件大小为 0x10000, 那么上位机发出来的顺序是
      // 00 00 01 00, 因此这里需要一个顺序的调换
      r=Serial.read();
      *(p+c)=r;
      c++;
      if DEBUG {
            mySerial.print(r);
            mySerial.println("");
      }   
      } //while (Serial.available() > 0)
} //while (c<4)

if DEBUG {
    mySerial.print("filesize=");
    mySerial.println(filesize);
}
   
//通知上位机继续发送
Serial.print('N');
}

void loop() {
byte buffer;
int i;
byte checksum;
Next:
//接收数据的字节数
int counter=0;
//接收超时的变量
unsigned long elsp=millis();
//如果接收数量小于缓冲区大小并且未超时,那么继续接收
while ((counter<BUFFERSIZE)&&(millis()-elsp<TIMEOUT))
    {
      while (Serial.available()>0)
      {
          buffer=Serial.read();
         if (DEBUG) {
            // mySerial.print(buffer,HEX);
            // mySerial.print(" ");
            // mySerial.println(counter,HEX);
         }
          counter++;
      } //while   
    }   
//如果接收数量不足退出上面的循环
if (counter!=BUFFERSIZE) {
    //通知上位机重新发送
    Serial.print("R");
    if DEBUG {
      mySerial.print("R");
    }   
}

//检查接收到的数据校验和
checksum=0;
for (i=0;i<BUFFERSIZE-1;i++)
    {
      checksum=checksum+buffer;
      //Serial.print(buffer);
      //Serial.print("   ");
      if DEBUG {
       //mySerial.print(buffer);
       //mySerial.print("   ");
      }
    }
//校验失败通知上位机重新发送
if (checksum!=0){
       Serial.print("R");
       if DEBUG {
            mySerial.print("R");
       }   
       goto Next;
    }

//如果当前收到的总数据小于文件大小,那么要求上位机继续发送
if (total<filesize)
    {
      Serial.print('N');
      //有效值只有 BUFFERSIZE-2
      total=total+BUFFERSIZE-2;
      if DEBUG {
            mySerial.print("N");
      }   
    }
    //否则停止
    else {
       if DEBUG {
            mySerial.print("Total received");
            mySerial.print(total);
       }   
      while (1==1) {}
    } //else

}调试的方法是开一个SoftwareSeiral,然后用Pin11接到USB 转串口的RX 上,同时共地。我发送一个 400K 左右的文件: 上面的方法优点是:1.   足够简单容易实现2.   对内存要求低缺点也是很明显:1.   接收端只能等待外面过来的文件大小,如果这一步骤出现问题,那么后面都会乱掉,换句话说,如果你的通讯信道足够糟糕,那么整体还是不稳定;2.   效率不高,如果使用16位的Buffer,那么有效的数据只有 14字节,14 /16=87.5%。资料上说Uno 的串口默认是 64Byte,如果用这么大的Buffer,效率可以达到(64-2)/64=96.9%。附件下载:Windows内部发送和接受代码和EXE(使用 128字节Buffer,速度挺快)Windows发送的EXE(16字节Buffer), Arduino 代码
页: [1]
查看完整版本: 设计一个文件传输的协议