xld0932 发表于 2022-5-1 18:20

MM32F3270通讯板之通过RS232接口实现Ymodem协议传输

#申请原创#   @21小跑堂




简介
Ymodem协议是Xmodem协议的进阶版本,具有传输速度快、传输稳定等优点,与Xmodem协议除了在传输起始和结束的数据帧不同之外,其它程序处理与Xmodem相近,不同的是接收到数据之后的处理,因为Ymodem添加了文件名、文件类型以及文件长度信息,所以Ymodem可以实现多个文件的批量接收和发送处理,文件长度可以有效的避免了对CTRLZ填充数据的处理。而我们平常所说的Ymodem协议一般是指Ymodem-1k,即传输数据包内容的大小为1024字节;除此之外还有Ymodem-G版本,它不带有CRC校验字段,所以不常用。

开始帧数据包我们在使用不同的上位机软件通过Ymodem协议来传输数据或文件时,对于传输数据包内容的大小一般定义为1024字节,但有些软件可以选择传输数据包内容的大小为128字节或者是1024字节,所以需要根据发送方发送帧数据的首字节是SOH(数值为0x01,代表数据包内容大小为128字节)还是STX(数值为0x02,代表数据包内容大小为1024字节)来进行判断和做相应的接收处理。
Ymodem协议中发送方向接收方发送的第一个数据包并非有效数据,而是文件的相关信息,是将文件名(包括文件扩展名)、文件的大小信息作打包成第一个数据包进行传输的。第一帧数据包格式如下:SOH + 0x00 + 0xFF + FileName.FileType + FileSize + CRC其中SOH是开始字符,每个数据包的有效数据长度有的是128字节的,有的则是1024字节,需要根据发送方发送的数据包的开始字符是SOH还是STX而定;比如在使用HyperTerminal工具进行传输时,文件信息的有效数据长度是128字节,在后面传输数据的有效数据长度则是1024字节;又比如使用SecureCRT工具进行传输时,文件信息的有效数据长度和后面传输数据的有效数据长度都可根据软件设定为128字节或者是1024字节,但在实际使用过程中有需要注意的,这在后面测试的时候会结合测试结果来举例说明。接下来的0x00表示数据包序号,下标值从0x00开始,在后面的数据包序号则是从0x01开始往上递增。0xFF是数据包序号的取反数值。FileName.FileType是文件名.文件类型,FileSize是文件大小,单位是字节;其中FileName.FileType和FileSize是以ASCII码形式表示的,在这两个字段后面都需要加上0x00表示该字段的结束。最后就是CRC校验娄值,在整包数据内容不足128字节时,也需要使用0x00来填充补齐。
例如第一帧数据包数据内容如下:0x01 0x00 0xFF 0x54 0x58 0x2E 0x74 0x78 0x74 0x00 0x33 0x36 0x30 0x00……从上面举例的数据中可以看出,第一个数据包的包序列号是0x00,数据序号取反值为0xFF,代表传输的是文档信息,紧跟数据包序号后面的是发送文件的文件名和文件类型TX.TXT(0x54 0x58 0x2E 0x74 0x78 0x74),再后面是文件传输数据的字节总数0x360字节(0x33 0x36 0x30),文件信息字段之间用0x00隔开,最后是填充字符和CRC校验(这边用……代替)。

结束帧数据包Ymodem协议的结束帧数据包根据使用不同的发送软件而定,我们在实际测试时使用了HyperTerminal和SecureCRT这两个工具,这两个工具对于Ymodem协议结束帧数据包格式定义如下:HyperTerminal :SOH + 0x00 + FF + 128个0x00 + CRCSecureCRT:SOH + 0x00 + 0xFF + 0x00 + 0x30 + 0x20 + 0x30 + 0x20 + 0x30 + 122个0x00 + CRC在结束帧数据包中同样以SOH作为开始字符,后面表示带有128字节的数据内容长度;结束帧数据包的序号为0x00 0xFF;在不同的发送软件差异在于对后面跟的数据内容不同而已。

传输过程1、传输开始是由接收方控制的,接收方发送开始字符C,然后进入等待第一帧数据包的状态,如果长时间没有回应,则会超时退出;2、而发送方开始时则处于等待开始字符C的状态下,发送方收到接收方发送的开始字符C之后,则会发送第一帧数据包后进入等待接收方回复ACK的状态,第一帧数据包内容即是文件相关的信息。3、接收方收到发送方发送的第一帧数据包后会进行CRC校验,如果校验结果正确则发送ACK回复发送方;4、发送方在收到接收方回复的ACK信息后,发送方又进入到等待文件开始传输的开始字符状态,即重新进入等待开始字符C的状态;(上述步骤接收方只是收到了发送方传输的文件信息,包括文件名、文件扩展名、文件大小等信息,之后将正式开启文件内容传输)5、此时接收方又发出一个开始字符C,开始准备接收文件内容,进入等待数据包的接收状态;6、发送方收到接收方发送的开始字符C后,开始进行第二帧数据包的发送,然后等待接收方的ACK确认信号;对于数据包内容格式的定义可以参考Xmodem协议,此部分与之相同;7、接收方在收到发送方的数据后会进行CRC校验,校验结果正确则会发送一个ACK回复给发送方,然后等待下一个数据包传送完毕,再进行校验并继续回复ACK/NAK应答,直到所有数据包发送完成;8、在数据传送完成后,发送方会发送EOT给接收方,第一次接收方需回复NAK应答(需要发送方进行二次确认),在发送方收到接收方回复的NAK信号后,会再次发送EOT给接收方,而接收方在第二次收到EOT后,就回复ACK应答给发送方,表示当前文件传输结束。接着接收方会再发送一个开始字符C来开启下一个文件数据的传输,如果发送方在没有后续文件需要传输的情况下, 发送方则会发送结束帧数据包,在等接收方回复ACK应答后, 正式结束数据传输。

实现功能通过Ymodem协议接收来自发送方的多个文件数据包,对其过程进行监控和打印消息输出;再通过软件代码构建几个需要传输的文件及不同的文件内容,能Ymodem协议发送给接收方进行保存,并查看文件中的数据正确与否。

实现代码由于字数限制,下面的代码仅是核心实现部分,完整的代码可参考附件中的源代码工程,包含详尽的中文注释,易于阅读和理解。
Ymodem校验部分及结束帧数据包判断uint16_t Ymodem_CalcCheckCRC(uint8_t *Buffer, uint16_t Length)
{
    uint32_t Result = 0;

    Length += 0x02;

    while(Length--)
    {
      uint8_t Data = (Length < 2) ? 0 : *Buffer++;

      for(uint8_t i = 0; i < 8; i++)
      {
            Result <<= 1;

            if(Data   & 0x00080) Result += 0x0001;

            Data   <<= 1;

            if(Result & 0x10000) Result ^= 0x1021;
      }
    }

    Result &= 0xFFFF;

    printf("\r\n\r\nCheck CRC:0x%04x\r\n\r\n", Result);

    return Result;
}

uint8_t Ymodem_CalcCheckSum(uint8_t *Buffer, uint16_t Length)
{
    uint16_t Result = 0;

    while(Length--)
    {
      Result += *Buffer++;
    }

    Result &= 0xFF;

    printf("\r\n\r\nCheck Sum:0x%02x\r\n\r\n", Result);

    return Result;
}


uint8_t Ymodem_CheckRxComplete(void)
{
#if USE_HYPER_TERMINAL
    if(Ymodem_Length == 128)
    {
      if((Ymodem_Number == 0x00) && (Ymodem_Number == 0xFF))
      {
            for(uint16_t i = 0; i < Ymodem_Length; i++)
            {
                if(Ymodem_Buffer != 0x00) return 0;
            }

            return 1;
      }
    }
#endif

#if USE_SECURE_CRT
    uint8_t compare;

    memset(compare, 0, sizeof(compare));

    compare = 0x30;
    compare = 0x20;
    compare = 0x30;
    compare = 0x20;
    compare = 0x30;

    if(Ymodem_Length == 128)
    {
      if((Ymodem_Number == 0x00) && (Ymodem_Number == 0xFF))
      {
            for(uint16_t i = 6; i < Ymodem_Length; i++)
            {
                if(Ymodem_Buffer != compare) return 0;
            }

            return 1;
      }
    }
#endif

    return 0;
}
Ymodem接收处理void Ymodem_RxHandler(void)
{
    uint32_t StartTryTimtout = 0;

    QUEUE_INIT(QUEUE_RS232_RX_IDX);

    Ymodem_State = YMODEM_RX_STATE_SOH;

    while(1)
    {
      if(QUEUE_EMPTY(QUEUE_RS232_RX_IDX) == 0)
      {
            uint8_t Data = QUEUE_READ(QUEUE_RS232_RX_IDX);

            printf("0x%02x ", Data);

            switch(Ymodem_State)
            {
                case YMODEM_RX_STATE_SOH:
                  if((Data == YMODEM_SOH) || (Data == YMODEM_STX))
                  {
                        Ymodem_Length= (Data == YMODEM_STX) ? 1024 : 128;
                        Ymodem_Index   = 0;
                        Ymodem_State   = YMODEM_RX_STATE_NUM;
                  }
                  else if(Data == YMODEM_EOT)
                  {
                        Ymodem_Index++;

                        if(Ymodem_Index == 1)
                        {
                            RS232_SendData(YMODEM_NAK);
                        }
                        else
                        {
                            Ymodem_Index = 0;

                            RS232_SendData(YMODEM_ACK);
                        }
                  }
                  else if(Data == YMODEM_CAN)
                  {
                        return;
                  }
                  else
                  {
                  }
                  break;

                case YMODEM_RX_STATE_NUM:
                  Ymodem_Number = Data;

                  if(Ymodem_Index == 2)
                  {
                        Ymodem_Index = 0;
                        Ymodem_State = YMODEM_RX_STATE_DAT;
                  }
                  break;

                case YMODEM_RX_STATE_DAT:
                  Ymodem_Buffer = Data;

                  if(Ymodem_Index == Ymodem_Length)
                  {
                        Ymodem_Index = 0;
                        Ymodem_State = YMODEM_RX_STATE_CHK;
                  }
                  break;

                case YMODEM_RX_STATE_CHK:
                  Ymodem_CheckData = Data;

                  if((Ymodem_CheckType == 1) && (Ymodem_Index == 1))
                  {
                        Ymodem_Index = 0;

                        if(Ymodem_Number == ((Ymodem_Number ^ 0xFF) & 0xFF))
                        {
                            if(Ymodem_CheckData == Ymodem_CalcCheckSum(Ymodem_Buffer, Ymodem_Length))
                            {
                              RS232_SendData(YMODEM_ACK);

                              if(Ymodem_CheckRxComplete() == 1) return;
                            }
                            else
                            {
                              RS232_SendData(YMODEM_NAK);
                            }
                        }
                        else
                        {
                            RS232_SendData(YMODEM_NAK);
                        }

                        Ymodem_State = YMODEM_RX_STATE_SOH;
                  }
                  else if((Ymodem_CheckType == 0) && (Ymodem_Index == 2))
                  {
                        Ymodem_Index = 0;

                        if(Ymodem_Number == ((Ymodem_Number ^ 0xFF) & 0xFF))
                        {
                            uint16_t Result = 0;

                            Result   = Ymodem_CheckData;
                            Result <<= 8;
                            Result|= Ymodem_CheckData;

                            if(Result == Ymodem_CalcCheckCRC(Ymodem_Buffer, Ymodem_Length))
                            {
                              RS232_SendData(YMODEM_ACK);

                              if(Ymodem_CheckRxComplete() == 1) return;
                            }
                            else
                            {
                              RS232_SendData(YMODEM_NAK);
                            }
                        }
                        else
                        {
                            RS232_SendData(YMODEM_NAK);
                        }

                        Ymodem_State = YMODEM_RX_STATE_SOH;
                  }
                  else
                  {
                  }
                  break;

                default:
                  break;
            }
      }
      else
      {
            if(StartTryTimtout == 0)
            {
                if(Ymodem_CheckType)
                {
                  RS232_SendData(YMODEM_NAK);
                }
                else
                {
                  RS232_SendData('C');
                }
            }

            StartTryTimtout = (StartTryTimtout + 1) % YMODEM_TRY_TIMEOUT;
      }
    }
}
Ymodem发送处理void Ymodem_TxHandler(void)
{
    for(uint16_t i = 0; i < Ymodem_FileNumber; i++)
    {
      Ymodem_SentLength = 0;
      Ymodem_FileLength = 100 + i * 50;

      for(uint16_t j = 0; j < Ymodem_FileLength; j++)
      {
            Ymodem_FileBuffer = 0x30 + (i % 10);
      }
    }

    QUEUE_INIT(QUEUE_RS232_RX_IDX);

    Ymodem_State   = YMODEM_TX_STATE_WAIT_FSOH;
    Ymodem_Length    = 128;
    Ymodem_FileIndex = 0;

    while(1)
    {
      switch(Ymodem_State)
      {
            case YMODEM_TX_STATE_WAIT_FSOH:

                if(QUEUE_EMPTY(QUEUE_RS232_RX_IDX) == 0)
                {
                  uint8_t Data = QUEUE_READ(QUEUE_RS232_RX_IDX);

                  printf("\r\nFSOH: 0x%02x", Data);

                  if((Data == 'C') || (Data == YMODEM_NAK))
                  {
                        Ymodem_CheckType = (Data == 'C') ? 0 : 1;
                        Ymodem_Number = 0x00;
                        Ymodem_Number = (Ymodem_Number ^ 0xFF) & 0xFF;
                        Ymodem_State   = YMODEM_TX_STATE_SEND_FILE;
                  }
                  else if(Data == YMODEM_CAN)
                  {
                        return;
                  }
                  else
                  {
                  }
                }
                break;

            case YMODEM_TX_STATE_SEND_FILE:
                memset(Ymodem_Buffer, 0, sizeof(Ymodem_Buffer));

                QUEUE_INIT(QUEUE_RS232_RX_IDX);

                if(Ymodem_FileIndex < Ymodem_FileNumber)
                {
                  Ymodem_Index = 0;

                  Ymodem_Buffer = '0';
                  Ymodem_Buffer = '0';
                  Ymodem_Buffer = '0' + (Ymodem_FileIndex % 10);
                  Ymodem_Buffer = '.';
                  Ymodem_Buffer = 't';
                  Ymodem_Buffer = 'x';
                  Ymodem_Buffer = 't';

                  Ymodem_Buffer = 0x00;

                  Ymodem_Buffer = '0' + ((Ymodem_FileLength / 100) % 10);
                  Ymodem_Buffer = '0' + ((Ymodem_FileLength / 10 ) % 10);
                  Ymodem_Buffer = '0' + ((Ymodem_FileLength / 1) % 10);
                }

                RS232_SendData(YMODEM_SOH);
               
                RS232_SendData(Ymodem_Number);
                RS232_SendData(Ymodem_Number);

                for(uint16_t i = 0; i < 128; i++)
                {
                  RS232_SendData(Ymodem_Buffer);
                }

                if(Ymodem_CheckType)
                {
                  RS232_SendData(Ymodem_CalcCheckSum(Ymodem_Buffer, 128));
                }
                else
                {
                  uint16_t Result = Ymodem_CalcCheckCRC(Ymodem_Buffer, 128);

                  RS232_SendData((Result >> 0x08) & 0xFF);
                  RS232_SendData((Result >> 0x00) & 0xFF);
                }

                Ymodem_State = YMODEM_TX_STATE_WAIT_FACK;
                break;

            case YMODEM_TX_STATE_WAIT_FACK:
                if(QUEUE_EMPTY(QUEUE_RS232_RX_IDX) == 0)
                {
                  uint8_t data = QUEUE_READ(QUEUE_RS232_RX_IDX);

                  printf("\r\nFACK: 0x%02x", data);

                  if(data == YMODEM_ACK)
                  {
                        if(Ymodem_FileIndex < Ymodem_FileNumber)
                        {
                            Ymodem_State = YMODEM_TX_STATE_WAIT_DSOH;
                        }
                        else
                        {
                            return;
                        }
                  }
                  else if(data == YMODEM_NAK)
                  {
                        Ymodem_State = YMODEM_TX_STATE_SEND_FILE;
                  }
                  else
                  {
                  }
                }
                break;

            case YMODEM_TX_STATE_WAIT_DSOH:
                if(QUEUE_EMPTY(QUEUE_RS232_RX_IDX) == 0)
                {
                  uint8_t data = QUEUE_READ(QUEUE_RS232_RX_IDX);

                  printf("\r\nDSOH: 0x%02x", data);

                  if((data == 'C') || (data == YMODEM_NAK))
                  {
                        Ymodem_CheckType = (data == 'C') ? 0 : 1;
                        Ymodem_Number = 0x00;
                        Ymodem_Number = (Ymodem_Number ^ 0xFF) & 0xFF;
                        Ymodem_State   = YMODEM_TX_STATE_SEND_DATA;
                  }
                  else if(data == YMODEM_CAN)
                  {
                        return;
                  }
                  else
                  {
                  }
                }
                break;

            case YMODEM_TX_STATE_SEND_DATA:
                memset(Ymodem_Buffer, 0, sizeof(Ymodem_Buffer));

                QUEUE_INIT(QUEUE_RS232_RX_IDX);

                Ymodem_Index = 0;
                Ymodem_Number++;
                Ymodem_Number = (Ymodem_Number ^ 0xFF) & 0xFF;

                while(Ymodem_SentLength < Ymodem_FileLength)
                {
                  if(Ymodem_Index < Ymodem_Length)
                  {
                        Ymodem_Buffer = Ymodem_FileBuffer++];
                  }
                  else
                  {
                        break;
                  }
                }

                while(Ymodem_Index < Ymodem_Length)
                {
                  Ymodem_Buffer = YMODEM_CTRLZ;
                }

                if(Ymodem_Length == 128)
                {
                  RS232_SendData(YMODEM_SOH);
                }
                else
                {
                  RS232_SendData(YMODEM_STX);
                }

                RS232_SendData(Ymodem_Number);
                RS232_SendData(Ymodem_Number);

                for(uint16_t i = 0; i < Ymodem_Length; i++)
                {
                  RS232_SendData(Ymodem_Buffer);
                }

                if(Ymodem_CheckType)
                {
                  RS232_SendData(Ymodem_CalcCheckSum(Ymodem_Buffer, Ymodem_Length));
                }
                else
                {
                  uint16_t Result = Ymodem_CalcCheckCRC(Ymodem_Buffer, Ymodem_Length);

                  RS232_SendData((Result >> 0x08) & 0xFF);
                  RS232_SendData((Result >> 0x00) & 0xFF);
                }

                Ymodem_State = YMODEM_TX_STATE_WAIT_DACK;
                break;

            case YMODEM_TX_STATE_WAIT_DACK:
                if(QUEUE_EMPTY(QUEUE_RS232_RX_IDX) == 0)
                {
                  uint8_t data = QUEUE_READ(QUEUE_RS232_RX_IDX);

                  printf("\r\nDACK: 0x%02x", data);

                  if(data == YMODEM_ACK)
                  {
                        if(Ymodem_SentLength == Ymodem_FileLength)
                        {
                            QUEUE_INIT(QUEUE_RS232_RX_IDX);

                            RS232_SendData(YMODEM_EOT);

                            Ymodem_State = YMODEM_TX_STATE_WAIT_EOT;
                        }
                        else
                        {
                            Ymodem_State = YMODEM_TX_STATE_SEND_DATA;
                        }
                  }
                  else if(data == YMODEM_NAK)
                  {
                        Ymodem_Number -= 1;
                        Ymodem_SentLength-= Ymodem_Length;
                        Ymodem_State = YMODEM_TX_STATE_SEND_DATA;
                  }
                  else
                  {
                  }
                }
                break;

            case YMODEM_TX_STATE_WAIT_EOT:
                if(QUEUE_EMPTY(QUEUE_RS232_RX_IDX) == 0)
                {
                  uint8_t data = QUEUE_READ(QUEUE_RS232_RX_IDX);

                  printf("\r\nEOT: 0x%02x", data);

                  if(data == YMODEM_NAK)
                  {
                        QUEUE_INIT(QUEUE_RS232_RX_IDX);

                        RS232_SendData(YMODEM_EOT);
                  }
                  else if(data == YMODEM_ACK)
                  {
                        Ymodem_FileIndex++;

                        Ymodem_State = YMODEM_TX_STATE_WAIT_FSOH;
                  }
                  else
                  {
                  }
                }
                break;

            default:
                break;
      }
    }
}

测试运行1、在MobaXterm终端上输入YMODEM 1命令,进行YMODEM协议数据传输
2、此时在SecureCRT软件上会打印出开始字符C的提示
3、点击SecureCRT软件Transfer->Send Ymodex...,在弹出的Select Files to Send using Ymodem窗口中添加需要传输的文件后,点击OK进行数据传输
4、SecureCRT在传输过程中会显示当前传输的进度、传输速度、报错等信息
5、在Ymodem传输完成后,我们可以在MobaXterm终端上看到打印出来的传输具体数据,如下图中包含了开始帧数据包、结束帧数据包、文件数据包、CRC校验、以及数据包大小为1024字节和128字节的两种情况
6、在SecureCRT软件中点击Options->Session Optins,在弹出窗口中的X/Y/Zmodem中查看Download目录,这就是后面我们接收文件存储的路径
7、在MobaXterm终端上输入YMODEM 2命令,进行YMODEM协议数据传输
8、点击SecureCRT软件Transfer->Receive Ymodex...此时SecureCRT软件会通过Ymodem协议接收来自MM32F3270通过RS232发送过来的文件数据包
9、在接收完成后,我们打开Download目录,发现刚刚接收到的文件都存放在这个路径下面
10、我们依次打开这些文件,查看文件内容,确认是我们示例程序中发送的文件以及对应的数据

附件软件工程源代码:参考资料:

xld0932 发表于 2022-5-1 19:09

Ymodem协议是Xmodem协议的升级版本,最大的特点是在Xmodem协议上增加多文件传输的功能,使得Ymodem协议传输具有更快速、稳定的特点。文中分享了在使用不同发送工具通过Ymodem协议传输文件数据时,对于结束帧数据包不同的分类处理,软件源代码工程支持Ymodem协议的完整功能,并进行了详细的测试及过程分享。

xld0932 发表于 2022-5-9 09:37

很多项目在做IAP升级的时候,会用至Ymodem、Xmodem或者Kermit等等这些协议来传输升级文件哦

fuqinyyy 发表于 2022-5-10 08:09

XModem,YModem,ZModem…

xld0932 发表于 2022-5-10 08:41

fuqinyyy 发表于 2022-5-10 08:09
XModem,YModem,ZModem…

嗯,已经分享了Xmodem、Ymodem、Kermit这三个串行文件传输协议在自制MM32F3270通讯板上的实现,Zmodem后面有机会熟悉了再分享……

两只袜子 发表于 2022-5-10 11:25

源码和工程资料都有了,很好的

xld0932 发表于 2022-5-10 12:36

两只袜子 发表于 2022-5-10 11:25
源码和工程资料都有了,很好的

qcliu 发表于 2022-6-2 17:31

第一次了解这个协议

coshi 发表于 2022-6-2 17:45

他的主要应用场景是什么呢

weifeng90 发表于 2022-6-2 18:24

详细教程,谢谢分享。

tpgf 发表于 2022-6-2 18:27

速度能快多少呀

drer 发表于 2022-6-2 18:34

请问是否是只能通过rs232啊

wiba 发表于 2022-6-2 18:48

多文件传输很牛啊

kxsi 发表于 2022-6-2 18:59

居然有这么多升级版本了啊

xld0932 发表于 2022-6-2 20:31

drer 发表于 2022-6-2 18:34
请问是否是只能通过rs232啊

传输协议可以应用在多种不同的接口上

xld0932 发表于 2022-6-2 20:32

wiba 发表于 2022-6-2 18:48
多文件传输很牛啊

xld0932 发表于 2022-6-2 20:32

kxsi 发表于 2022-6-2 18:59
居然有这么多升级版本了啊

carpsnow 发表于 2022-6-28 21:01

还是第一次听说这个协议

NOo02 发表于 2022-9-14 14:11

想学习一下楼主用Ymodem协议传输文件到spiflash的方法
页: [1]
查看完整版本: MM32F3270通讯板之通过RS232接口实现Ymodem协议传输