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

Arduino中文社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 4597|回复: 0

设计一个文件传输的协议

[复制链接]
发表于 2017-8-3 21:58 | 显示全部楼层 |阅读模式
很多时候,我们需要从串口无损传输一个文件。但是对于串口来说,经常会发生丢失数据或者是数据错误的情况。因此,需要有一个协议来保证传输的正确性。这里设计一个简单的协议。
首先,打开接收端等待传输,发送端传输一个4字节的文件长度。之后的数据都是以N为单位(比如:16字节或者128字节等等)进行发送。
image002.jpg
数据包的长度为 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 直接就可以调用串口非常方便。
image003.png
我传输了一个200K的内容,之后对比过,发送和接受内容是相同的。我将代码直接打包,有兴趣的朋友可以研究一下(Delphi XE2编译)。
image005.png
这是上位机的发送部分,每次收到N或者R后会根据情况进行发送
[mw_shl_code=pascal,true]procedure TForm2.ComPort1RxChar(Sender: TObject; Count: Integer);
var
  s,t: String;

  aSize,i:integer;
  checksum:byte;
begin
  ComPort1.ReadStr(s, Count);
  if s[Count]='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[BUFFERSIZE-2]:=checksum;
           //放置顺序号
           Buffer[BUFFERSIZE-1]:=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[Count]='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;[/mw_shl_code]

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

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

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

char buffer[BUFFERSIZE];

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[BUFFERSIZE];
  int i;
  byte checksum;
Next:
  //接收数据的字节数
  int counter=0;
  //接收超时的变量
  unsigned long elsp=millis();
  //如果接收数量小于缓冲区大小并且未超时,那么继续接收
  while ((counter<BUFFERSIZE)&&(millis()-elsp<TIMEOUT))
    {
      while (Serial.available()>0)
        {
          buffer[counter]=Serial.read();
         if (DEBUG) {
            // mySerial.print(buffer[counter],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

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

image004.jpg
image006.jpg
image007.png
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|Archiver|手机版|Arduino中文社区

GMT+8, 2024-11-28 14:38 , Processed in 0.083044 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表