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系统的小伙伴很有体会哦
【Kermit协议传输流程图】中的流程图片太模糊了,但是附件 【Kermit流程图.pdf】是非常高清的。 推荐用下 PDF24 软件(https://tools.pdf24.org/zh/),在阅读器单击鼠标右键, 可以直接将 PDF 的页面复制为图片。
www5911839 发表于 2022-5-5 13:06
【Kermit协议传输流程图】中的流程图片太模糊了,但是附件 【Kermit流程图.pdf】是非常高清的。 推 ...
是原图片太大了,发布帖子的时候,系统可能自动做了缩放;原图肯定是清晰的;所以在附件中提供了PDF版本的 Kermit协议应用很广泛,尤其在Linux系统中用来进行文件传输;当前找到资料最多的就是教如何在Linux下的安装和使用,但描述在MCU上如何使用的分享却很少;本帖主要分享了基于MM32F3270芯片,使用SecureCRT软件通过Kermit协议进行文件数据传输的实现和测试过程,对基本通讯概念和注意事项做了描述,并绘制了传输流程图。
kermit协议跟XMODEM、YMODEM和ZMODEM协议,有什么区别。。。 yklstudent 发表于 2022-5-5 21:05
kermit协议跟XMODEM、YMODEM和ZMODEM协议,有什么区别。。。
对于我的理解就是Xmodem仅支持单个文件的收发、Ymodem支持多个文件的收发、Zmodem支持断点续传、而Kermit传输的都是可见字符,有些需要对数据进行处理后再传输。每个人对不同点的关注度不一样,你可以先尝试去了解每一个协议后,再去做详细的比对
学习了,谢谢分享。 weifeng90 发表于 2022-5-6 08:16
学习了,谢谢分享。
这与XMODEM和YMODEM有什么区别 gouguoccc 发表于 2022-5-7 08:20
这与XMODEM和YMODEM有什么区别
6楼解释了{:tongue:} 那看来没有X、Y、ZModem实用。 本帖最后由 xld0932 于 2022-5-9 18:59 编辑
chenjun89 发表于 2022-5-8 17:07
那看来没有X、Y、ZModem实用。
Uboot众所周知了,可以了解一下,里面有用到Kermit 这种协议是不是就是标准呢? 我觉得协议这种可以自己规约,不过有现成的那是极好的,哈哈 话说,kermit这个协议用的人多么?一般都用在哪儿啊? 232的话,自己设定个程序协议也行的,也比较好用 其实数据包用你这种协议挺好,自己不用定制协议了 su1yirg 发表于 2022-5-20 18:57
其实数据包用你这种协议挺好,自己不用定制协议了
嗯,这些都是通用的串行文件传输协议,有配套的上位机软件
这种协议是有数据大小限制的么? 听起来还不错,等以后有时间,研究研究这个协议
页:
[1]
2