#申请原创# @21小跑堂
Kermit协议是由哥伦比亚大学开发的,它是一个面向数据包的协议,在各种结构不兼容计算机之间实现了相互通讯的功能。
Kermit协议采用的是发送等待应答(ARQ)的通讯方式,即发送方发送一个信息包后,等待接收方的回复;接收方可以通过回复ACK应答请求下一个数据包的传输,也可以通过回复NAK应答请求重新发送刚刚的数据包。
接收和发送双方按照ARQ规则进行数据传输,一直到整个文件传送完成为止;然后发送方发送一个特别的文件结束数据包;若还有其它文件数据需要传输,则会接着传输下一个文件的文件头数据包,当所有的文件都传输完成之后,发送方就会发送一个传输结束的数据包表示本次的传输结束。
Kermit协议中的数据包规定由6个字段组成,分别如下表所示: 对于数据字段的长度没有固定,数据字段的内容随着数据包类型不同而变化;只有在DATA数据包中的数据字段,才包含被传输文件的数据内容。所有的数据包内容都是由ASCII码[0, 127]字符组成的,当然也存在一些特殊使用情况,后面会提到;但是都遵循一个原则,就是在数据包中除MARK字段外,其它所有的字段内容都只能由可见(可打印)的ASCII码[0x20, 0x7E]字符组成。
Kermit数据包中的字段定义 MARK:数据包开始标志,一般为CTRT-A(SOH,即0x01) LEN:数据包长度,指示该字段之后的字节数,上限值为96 SEQ:数据包序号,以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取余数得到的数值。
控制字段中的控制字符 当一个字符的取值在[0x00, 0x20]之间或为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类型数据包 MAXL:接收数据包的最大长度,最大值为94 TIME:等待超时时间,单位为秒 NPAD:填充字符 PADC:填充字符<NULL>或<DEL> EOL:数据包结束字符<CR> QCTL:控制前缀字符# QBIN:非ASCII前缀字符,传送二进制文件时使用 CHKT:校验和类型,单、双字符校验和、三字符的CRC校验分别用123来表示,默认值为1 REPT:重复前缀字符,通常为~ CAPAS:位模,表示Kermit的功能 接收方的Kermit用ACK数据包应答发送方的S类型数据包,以告诉对方确定了哪些初始化参数值,至此通信双方进行文件传输时的功能就确定下来了。需要注意的一点:当然如果接收方回复发送方的起始数据包的ACK中,DATA字段为空(就是不带有确认参数),则表示接收方要求发送方以默认值配置进行数据传输。
Kermit协议传输流程图
Kermit接收数据代码 uint8_t Kermit_RxSubHandler(uint8_t Data)
{
static uint8_t Kermit_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[KermitRxPacket.Index++] = 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[0]));
printf("\r\nTIME : %d", KERMIT_UNCHAR(KermitRxPacket.DATA[1]));
printf("\r\nNPAD : %d", KERMIT_UNCHAR(KermitRxPacket.DATA[2]));
printf("\r\nPADC : 0x%02x", KERMIT_UNCHAR(KermitRxPacket.DATA[3]));
printf("\r\nEOL : 0x%02x", KERMIT_UNCHAR(KermitRxPacket.DATA[4]));
printf("\r\nQCTL : %c", (KermitRxPacket.DATA[5]));
printf("\r\nQBIN : %c", (KermitRxPacket.DATA[6]));
printf("\r\nCHKT : %c", (KermitRxPacket.DATA[7]));
printf("\r\nREPT : %c", (KermitRxPacket.DATA[8]));
printf("\r\nCAPAS : 0x%02x", KERMIT_UNCHAR(KermitRxPacket.DATA[9]));
printf("\r\nWIND0 : %d", KERMIT_UNCHAR(KermitRxPacket.DATA[10]));
printf("\r\nMAXLX1 : %d", KERMIT_UNCHAR(KermitRxPacket.DATA[11]));
printf("\r\nMAXLX2 : %d", KERMIT_UNCHAR(KermitRxPacket.DATA[12]));
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[13] =
{
'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[i]); CheckSum += StartPacket[i];
}
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[20];
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[i]); CheckSum += FileName[i];
}
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_t Kermit_TxState = 0;
uint8_t Kermit_TxSEQ = 0;
uint8_t Kermit_TxFinish = 0;
uint8_t Kermit_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发送数据测试
发送单个文件数据
发送多个文件数据
附件
续 本文是基于SecureCRT软件进行测试Kermit协议进行收发数据的。Kermit应用很广泛,经常使用Linux系统的小伙伴很有体会哦
|