#申请原创# @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系统的小伙伴很有体会哦 
|