xld0932 发表于 2022-5-5 11:53

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

#申请原创#   @21小跑堂




Kermit协议是由哥伦比亚大学开发的,它是一个面向数据包的协议,在各种结构不兼容计算机之间实现了相互通讯的功能。
Kermit协议采用的是发送等待应答(ARQ)的通讯方式,即发送方发送一个信息包后,等待接收方的回复;接收方可以通过回复ACK应答请求下一个数据包的传输,也可以通过回复NAK应答请求重新发送刚刚的数据包。
接收和发送双方按照ARQ规则进行数据传输,一直到整个文件传送完成为止;然后发送方发送一个特别的文件结束数据包;若还有其它文件数据需要传输,则会接着传输下一个文件的文件头数据包,当所有的文件都传输完成之后,发送方就会发送一个传输结束的数据包表示本次的传输结束。
Kermit协议中的数据包规定由6个字段组成,分别如下表所示:
MARKLENSEQTYPEDATACHECK
对于数据字段的长度没有固定,数据字段的内容随着数据包类型不同而变化;只有在DATA数据包中的数据字段,才包含被传输文件的数据内容。所有的数据包内容都是由ASCII码字符组成的,当然也存在一些特殊使用情况,后面会提到;但是都遵循一个原则,就是在数据包中除MARK字段外,其它所有的字段内容都只能由可见(可打印)的ASCII码字符组成。

Kermit数据包中的字段定义MARK:数据包开始标志,一般为CTRT-A(SOH,即0x01)LEN:数据包长度,指示该字段之后的字节数,上限值为96SEQ:数据包序号,以64为模循环使用,用来检测丢失或重复的数据包TYPE:数据包类型,用单个可打印的ASCII字符来表示:D:DATA数据Y:ACK应答N:NAK应答S:开始传输/初始化传输B:中断传输/传输结束(EOT)F:文件头Z:文件结束(EOF)E:错误T:保留X:在屏幕上显示文本A:文件属性DATA:需要发送的数据内容CHECK:数据包校验值,计算不包括MARK字段。默认的校验值是一个单字节的算术校验和,但它也可以选择两字节算术校验和、或者是三字节16位的CRC校验。如果X是数据包的校验和,那么则有CHECK = char((X + ((X AND 0xC0) / 0x40)) AND 0x3F)。这里所谓的文件数据包算术检验和,是对数据字段的内容,以及LEN字段和SEQ字段累加计算后对256取余数得到的数值。

控制字段中的控制字符当一个字符的取值在之间或为0x7F时,便会认为这是一个控制字符。在传送之前需要用char(X) = X + 0x20这种方式将对于控制字符转换成可打印的字符;再等到字符接收到之后,再通过unchar(X) = X - 0x20这种方式把控制字符转换回来即可。需要使用到这种编解码处理方式的字段有LEN、SEQ和CHECK这3个字段。我们可以看出,如果把char函数的形参X赋值一个比0x5F大的数值,就会得到一个比0x7F大的数值,而大于0x7F数值的ASCII码字符是非可见字符,这不符合Kermit协议要求的可见ASCII码字符这个要求;而0x5F在char运算之后变成了0x7F,0x7F本身代表的就是DEL这个控制字符;所以X的实际上限值就被限定到0x5E,所以得到了传输数据字段的长度上限就是94个字节。

数据字段中的控制字符Kermit协议对在数据字段中的控制字符采用的是所谓的前缀编码,就是在控制字符前面加上一个可打印的字符#,再将控制字符同0x40进行异或运算。比如对13的编码就是#53。

在Kermit数据中数据字节的最高位编码许多系统上,串行通讯的数据格式在本系统内是固定不变的。在这种情况下,程序常常没有办法来控制数据格式。如果碰巧是一个7位带奇偶校验位的系统,那么要传送8位二进制文件显然必须作某种形式的编码。这里采用了类似于上述前缀编码的方法:如果一个字符的最高位为1,就在它的前面插入一个可打印字符&,并且把这个字节同0x7F相与,使它变成一个可打印的字符。

启动传输启动传输的方法是当发送方收到接收方的NAK应答后,开始发送第一个数据包(S类型数据);在发送方发送出第一个数据包之前,接收方会间隔固定时间不断的发送NAK数据包。第一个数据包就是下面提到的起始数据包,它包含着发送方自身对某些重要传输参数的设定。接收方收到起始数据包之后会把自己的设定方案再包含在ACK数据包中作为对起始数据包的应答传输给发送方。

S类型数据包
MAXLTIMENPADPADCEOLQCTLQBINCHKTREPTCAPAS
MAXL:接收数据包的最大长度,最大值为94TIME:等待超时时间,单位为秒NPAD:填充字符PADC:填充字符<NULL>或<DEL>EOL:数据包结束字符<CR>QCTL:控制前缀字符#QBIN:非ASCII前缀字符,传送二进制文件时使用CHKT:校验和类型,单、双字符校验和、三字符的CRC校验分别用123来表示,默认值为1REPT:重复前缀字符,通常为~CAPAS:位模,表示Kermit的功能接收方的Kermit用ACK数据包应答发送方的S类型数据包,以告诉对方确定了哪些初始化参数值,至此通信双方进行文件传输时的功能就确定下来了。需要注意的一点:当然如果接收方回复发送方的起始数据包的ACK中,DATA字段为空(就是不带有确认参数),则表示接收方要求发送方以默认值配置进行数据传输。

Kermit协议传输流程图

Kermit接收数据代码uint8_t Kermit_RxSubHandler(uint8_t Data)
{
    static uint8_tKermit_RxState = 0;
    static uint32_t Kermit_RxCheck = 0;

    uint8_t Kermit_Result = 0;

    switch(Kermit_RxState)
    {
      case 0x00:/* MARK */
            if(Data == 0x01)
            {
                Kermit_RxState      = 0x01;
                Kermit_RxCheck      = 0x00;
                KermitRxPacket.MARK = Data;

                printf("\r\nMARK: 0x%02x", Data);
            }
            break;

      case 0x01:/* LEN */
            Kermit_RxState   = 0x02;
            Kermit_RxCheck    += Data;
            KermitRxPacket.LEN = Data;

            printf("\r\nLEN   : %d", KERMIT_UNCHAR(Data));
            break;

      case 0x02:/* SEQ */
            Kermit_RxState   = 0x03;
            Kermit_RxCheck    += Data;
            KermitRxPacket.SEQ = Data;

            printf("\r\nSEQ   : %d", KERMIT_UNCHAR(Data));
            break;

      case 0x03:/* TYPE */
            Kermit_RxState       = 0x04;
            Kermit_RxCheck      += Data;
            KermitRxPacket.TYPE= Data;
            KermitRxPacket.Index = 0x00;

            printf("\r\nTYPE: %c\r\nDATA: ", Data);
            break;

      case 0x04:/* DATA & CHECK */
            if((KermitRxPacket.Index + 3) == KERMIT_UNCHAR(KermitRxPacket.LEN))
            {
                Kermit_RxState       = 0x05;
                KermitRxPacket.CHECK = Data;

                printf("\r\nCHECK : [%c]", Data);
            }
            else
            {
                Kermit_RxCheck += Data;
                KermitRxPacket.DATA = Data;

                printf("%c", Data);
            }
            break;

      case 0x05:/* EOL */
            if(Data == 0x0D)
            {
                Kermit_RxCheck %= 256;

                printf("\r\nCALC: [%c]", KERMIT_TOCHAR((Kermit_RxCheck + ((Kermit_RxCheck & 0xC0) / 0x40)) & 0x3F));

                if(KermitRxPacket.CHECK == KERMIT_TOCHAR((Kermit_RxCheck + ((Kermit_RxCheck & 0xC0) / 0x40)) & 0x3F))
                {
                  Kermit_Result = 0x01;

                  printf("\tSuccess!");
                }
                else
                {
                  Kermit_Result = 0x02;

                  printf("\tError!!!");
                }

                Kermit_RxState   = 0x00;
                KermitRxPacket.EOL = Data;

                printf("\r\nEOL   : 0x%02x\r\n", Data);
            }
            else
            {
                printf("\r\n%c", Data);
            }
            break;

      default:
            Kermit_RxState = 0x00;
            break;
    }

    return Kermit_Result;
}

void Kermit_RxHandler(void)
{
    printf("\r\n%s\r\n", __FUNCTION__);

    QUEUE_INIT(QUEUE_RS232_RX_IDX);

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

            if(Result != 0)
            {
                if(KermitRxPacket.TYPE == 'S')
                {
                  printf("\r\nMAXL   : %d",      KERMIT_UNCHAR(KermitRxPacket.DATA));
                  printf("\r\nTIME   : %d",      KERMIT_UNCHAR(KermitRxPacket.DATA));
                  printf("\r\nNPAD   : %d",      KERMIT_UNCHAR(KermitRxPacket.DATA));
                  printf("\r\nPADC   : 0x%02x",    KERMIT_UNCHAR(KermitRxPacket.DATA));
                  printf("\r\nEOL    : 0x%02x",    KERMIT_UNCHAR(KermitRxPacket.DATA));
                  printf("\r\nQCTL   : %c",      (KermitRxPacket.DATA));
                  printf("\r\nQBIN   : %c",      (KermitRxPacket.DATA));
                  printf("\r\nCHKT   : %c",      (KermitRxPacket.DATA));
                  printf("\r\nREPT   : %c",      (KermitRxPacket.DATA));
                  printf("\r\nCAPAS: 0x%02x",    KERMIT_UNCHAR(KermitRxPacket.DATA));
                  printf("\r\nWIND0: %d",      KERMIT_UNCHAR(KermitRxPacket.DATA));
                  printf("\r\nMAXLX1 : %d",      KERMIT_UNCHAR(KermitRxPacket.DATA));
                  printf("\r\nMAXLX2 : %d",      KERMIT_UNCHAR(KermitRxPacket.DATA));
                  printf("\r\n");
                }

                if((KermitRxPacket.TYPE == 'S') || (KermitRxPacket.TYPE == 'F') ||
                   (KermitRxPacket.TYPE == 'D') || (KermitRxPacket.TYPE == 'Z') ||
                   (KermitRxPacket.TYPE == 'B') || (KermitRxPacket.TYPE == 'E'))
                {
                  uint32_t CheckSum = 0;

                  RS232_SendData(KermitRxPacket.MARK);
                  RS232_SendData(KERMIT_TOCHAR(0x03));CheckSum += KERMIT_TOCHAR(0x03);
                  RS232_SendData(KermitRxPacket.SEQ );CheckSum += KermitRxPacket.SEQ;

                  if(Result == 1)
                  {
                        RS232_SendData('Y');            CheckSum += 'Y';
                  }
                  else
                  {
                        RS232_SendData('N');            CheckSum += 'N';
                  }

                  CheckSum %= 256;

                  RS232_SendData(KERMIT_TOCHAR((CheckSum + ((CheckSum & 0xC0) / 0x40)) & 0x3F));

                  printf("\r\n0x%02x %d %d %c %c", KermitRxPacket.MARK,0x03,KERMIT_UNCHAR(KermitRxPacket.SEQ),
                                                'Y', KERMIT_TOCHAR((CheckSum + ((CheckSum & 0xC0) / 0x40)) & 0x3F));

                  printf("\r\n");
                  printf("\r\n");
                }

                if(KermitRxPacket.TYPE == 'B')
                {
                  printf("\r\nKermitRxFinish"); return;
                }

                if(KermitRxPacket.TYPE == 'E')
                {
                  printf("\r\nKermitRxCancle"); return;
                }
            }
      }
    }
}

Kermit接收数据测试
接收单个文件数据
接收多个文件数据

Kermit发送数据代码uint8_t Kermit_WaitReponse(uint8_t Data)
{
    uint8_t Result = 0;

    if(Kermit_RxSubHandler(Data) != 0)
    {
      switch(KermitRxPacket.TYPE)
      {
            case 'Y': Result = 1; break;
            case 'N': Result = 2; break;
            case 'E': Result = 3; break;
            default : break;
      }
    }

    return Result;
}

void Kermit_TxSubHandler(uint8_t State, uint8_t TxSEQ)
{
    static uint8_t Kermit_FileName = 0;
    static uint8_t Kermit_FileSize = 0;

    switch(State)
    {
      case 0x01:/* S */
      {
            printf("\r\nSend S\r\n");

            uint32_t CheckSum = 0;

            uint8_t StartPacket =
            {
                'p',    /* MAXL   */
                '*',    /* TIME   */
                ' ',    /* NPAD   */
                '@',    /* PADC   */
                '-',    /* EOL    */
                '#',    /* QCTL   */
                'Y',    /* QBIN   */
                '1',    /* CHKT   */
                '~',    /* REPT   */
                '*',    /* CAPAS*/
                '!',    /* WIND0*/
                'K',    /* MAXLX1 */
                '*',    /* MAXLX2 */
            };

            RS232_SendData(0x01);
            RS232_SendData(KERMIT_TOCHAR(0x03+0x0D));   CheckSum += KERMIT_TOCHAR(0x03+0x0D);
            RS232_SendData(KERMIT_TOCHAR(TxSEQ));       CheckSum += KERMIT_TOCHAR(TxSEQ);
            RS232_SendData('S');                        CheckSum += 'S';

            for(uint8_t i = 0; i < sizeof(StartPacket); i++)
            {
                RS232_SendData(StartPacket);         CheckSum += StartPacket;
            }

            CheckSum %= 256;

            RS232_SendData(KERMIT_TOCHAR((CheckSum + ((CheckSum & 0xC0) / 0x40)) & 0x3F));

            RS232_SendData(0x0D);
      }
      break;

      case 0x02:/* F */
      {
            printf("\r\nSend F\r\n");

            uint32_t CheckSum = 0;

            char FileName;
            memset(FileName, 0, sizeof(FileName));

            sprintf(FileName, "%d.TXT", Kermit_FileName);

            Kermit_FileSize = (Kermit_FileName + 1) * 20;

            RS232_SendData(0x01);
            RS232_SendData(KERMIT_TOCHAR(0x03+strlen(FileName))); CheckSum += KERMIT_TOCHAR(0x03+strlen(FileName));
            RS232_SendData(KERMIT_TOCHAR(TxSEQ));               CheckSum += KERMIT_TOCHAR(TxSEQ);
            RS232_SendData('F');                                  CheckSum += 'F';

            for(uint8_t i = 0; i < strlen(FileName); i++)
            {
                RS232_SendData(FileName);                      CheckSum += FileName;
            }

            CheckSum %= 256;

            RS232_SendData(KERMIT_TOCHAR((CheckSum + ((CheckSum & 0xC0) / 0x40)) & 0x3F));

            RS232_SendData(0x0D);
      }
      break;

      case 0x03:/* D */
      {
            printf("\r\nSend D\r\n");

            uint32_t CheckSum = 0;

            RS232_SendData(0x01);
            RS232_SendData(KERMIT_TOCHAR(0x03+Kermit_FileSize));CheckSum += KERMIT_TOCHAR(0x03+Kermit_FileSize);
            RS232_SendData(KERMIT_TOCHAR(TxSEQ));               CheckSum += KERMIT_TOCHAR(TxSEQ);
            RS232_SendData('D');                                  CheckSum += 'D';

            for(uint8_t i = 0; i < Kermit_FileSize; i++)
            {
                RS232_SendData(0x20 + i);                         CheckSum += (0x20 + i);
            }

            CheckSum %= 256;

            RS232_SendData(KERMIT_TOCHAR((CheckSum + ((CheckSum & 0xC0) / 0x40)) & 0x3F));

            RS232_SendData(0x0D);
      }
      break;

      case 0x04:/* Z */
      {
            printf("\r\nSend Z\r\n");

            uint32_t CheckSum = 0;

            RS232_SendData(0x01);
            RS232_SendData(KERMIT_TOCHAR(0x03));    CheckSum += KERMIT_TOCHAR(0x03);
            RS232_SendData(KERMIT_TOCHAR(TxSEQ));   CheckSum += KERMIT_TOCHAR(TxSEQ);
            RS232_SendData('Z');                  CheckSum += 'Z';

            CheckSum %= 256;

            RS232_SendData(KERMIT_TOCHAR((CheckSum + ((CheckSum & 0xC0) / 0x40)) & 0x3F));

            RS232_SendData(0x0D);

            Kermit_FileName = (Kermit_FileName + 1) % 4;
      }
      break;

      case 0x05:/* B */
      {
            printf("\r\nSend B\r\n");

            uint32_t CheckSum = 0;

            RS232_SendData(0x01);
            RS232_SendData(KERMIT_TOCHAR(0x03));    CheckSum += KERMIT_TOCHAR(0x03);
            RS232_SendData(KERMIT_TOCHAR(TxSEQ));   CheckSum += KERMIT_TOCHAR(TxSEQ);
            RS232_SendData('B');                  CheckSum += 'B';

            CheckSum %= 256;

            RS232_SendData(KERMIT_TOCHAR((CheckSum + ((CheckSum & 0xC0) / 0x40)) & 0x3F));

            RS232_SendData(0x0D);
      }
      break;

      default:
            break;
    }
}

void Kermit_TxHandler(void)
{
    uint8_tKermit_TxState= 0;
    uint8_tKermit_TxSEQ    = 0;
    uint8_tKermit_TxFinish = 0;

    uint8_tKermit_FileNumber = 4;
    uint32_t Kermit_WaitTime   = 0;

    printf("\r\n%s\r\n", __FUNCTION__);

    QUEUE_INIT(QUEUE_RS232_RX_IDX);

    while(1)
    {
      Kermit_TxSubHandler(Kermit_TxState, Kermit_TxSEQ);

      Kermit_WaitTime = 0;

      do
      {
            if(QUEUE_EMPTY(QUEUE_RS232_RX_IDX) == 0)
            {
                uint8_t Result = Kermit_WaitReponse(QUEUE_READ(QUEUE_RS232_RX_IDX));

                switch(Result)
                {
                  case 0x01:/* ACK */
                        switch(Kermit_TxState)
                        {
                            case 0x01: Kermit_TxState= 0x02; break;   /* S -> F */
                            case 0x02: Kermit_TxState= 0x03; break;   /* F -> D */
                            case 0x03: Kermit_TxState= 0x04; break;   /* D -> Z */

                            case 0x04:
                              Kermit_FileNumber--;

                              if(Kermit_FileNumber == 0)
                              {
                                    Kermit_TxState= 0x05;             /* Z -> B */
                              }
                              else
                              {
                                    Kermit_TxState= 0x02;             /* Z -> F */
                              }
                              break;

                            case 0x05: Kermit_TxFinish = 0x01; break;   /* B -> Finish */
                            default:
                              break;
                        }

                        Kermit_TxSEQ = (Kermit_TxSEQ + 0x01) % 0x40;
                        break;

                  case 0x02:/* NAK */
                        Kermit_TxState = 0x01;
                        break;

                  case 0x03:/* CANCLE */
                        Kermit_TxFinish = 0x02;
                        break;

                  default:
                        break;
                }

                if(Kermit_TxFinish == 0x01)
                {
                  printf("\r\nKermitTxFinish"); return;
                }

                if(Kermit_TxFinish == 0x02)
                {
                  printf("\r\nKermitTxCancle"); return;
                }

                if(Result != 0) Kermit_WaitTime = 100001;
            }
            else
            {
                SysTick_DelayMS(1);
            }

      }while(Kermit_WaitTime++ < 10000);
    }
}

Kermit发送数据测试
发送单个文件数据
发送多个文件数据

附件软件工程源代码:Kermit协议:Kermit传输流程图:Kermit收发数据包:Kermit参考资料:
续本文是基于SecureCRT软件进行测试Kermit协议进行收发数据的。Kermit应用很广泛,经常使用Linux系统的小伙伴很有体会哦

www5911839 发表于 2022-5-5 13:06

【Kermit协议传输流程图】中的流程图片太模糊了,但是附件 【Kermit流程图.pdf】是非常高清的。 推荐用下 PDF24 软件(https://tools.pdf24.org/zh/),在阅读器单击鼠标右键, 可以直接将 PDF 的页面复制为图片。

xld0932 发表于 2022-5-5 13:36

www5911839 发表于 2022-5-5 13:06
【Kermit协议传输流程图】中的流程图片太模糊了,但是附件 【Kermit流程图.pdf】是非常高清的。 推 ...

是原图片太大了,发布帖子的时候,系统可能自动做了缩放;原图肯定是清晰的;所以在附件中提供了PDF版本的

xld0932 发表于 2022-5-5 13:37

Kermit协议应用很广泛,尤其在Linux系统中用来进行文件传输;当前找到资料最多的就是教如何在Linux下的安装和使用,但描述在MCU上如何使用的分享却很少;本帖主要分享了基于MM32F3270芯片,使用SecureCRT软件通过Kermit协议进行文件数据传输的实现和测试过程,对基本通讯概念和注意事项做了描述,并绘制了传输流程图。

yklstudent 发表于 2022-5-5 21:05

kermit协议跟XMODEM、YMODEM和ZMODEM协议,有什么区别。。。

xld0932 发表于 2022-5-5 21:30

yklstudent 发表于 2022-5-5 21:05
kermit协议跟XMODEM、YMODEM和ZMODEM协议,有什么区别。。。

对于我的理解就是Xmodem仅支持单个文件的收发、Ymodem支持多个文件的收发、Zmodem支持断点续传、而Kermit传输的都是可见字符,有些需要对数据进行处理后再传输。每个人对不同点的关注度不一样,你可以先尝试去了解每一个协议后,再去做详细的比对

weifeng90 发表于 2022-5-6 08:16

学习了,谢谢分享。

xld0932 发表于 2022-5-6 08:29

weifeng90 发表于 2022-5-6 08:16
学习了,谢谢分享。

gouguoccc 发表于 2022-5-7 08:20

这与XMODEM和YMODEM有什么区别

xld0932 发表于 2022-5-7 08:28

gouguoccc 发表于 2022-5-7 08:20
这与XMODEM和YMODEM有什么区别

6楼解释了{:tongue:}

chenjun89 发表于 2022-5-8 17:07

那看来没有X、Y、ZModem实用。

xld0932 发表于 2022-5-8 22:17

本帖最后由 xld0932 于 2022-5-9 18:59 编辑

chenjun89 发表于 2022-5-8 17:07
那看来没有X、Y、ZModem实用。
Uboot众所周知了,可以了解一下,里面有用到Kermit

liu96jp 发表于 2022-5-20 16:47

这种协议是不是就是标准呢?

t1ngus4 发表于 2022-5-20 17:02

我觉得协议这种可以自己规约,不过有现成的那是极好的,哈哈

ex7s4 发表于 2022-5-20 17:35

话说,kermit这个协议用的人多么?一般都用在哪儿啊?

q1ngt12 发表于 2022-5-20 18:23

232的话,自己设定个程序协议也行的,也比较好用

su1yirg 发表于 2022-5-20 18:57

其实数据包用你这种协议挺好,自己不用定制协议了

xld0932 发表于 2022-5-20 19:56

su1yirg 发表于 2022-5-20 18:57
其实数据包用你这种协议挺好,自己不用定制协议了

嗯,这些都是通用的串行文件传输协议,有配套的上位机软件

tax2r6c 发表于 2022-5-22 08:16

这种协议是有数据大小限制的么?

lamanius 发表于 2022-5-22 08:45

听起来还不错,等以后有时间,研究研究这个协议
页: [1] 2
查看完整版本: MM32F3270通讯板之通过RS232接口实现Kermit协议传输