<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
一、程序代码编写
1、主流程设计
在命令识别程序中,当用户输入“fsend path”,表明要传输文件到目标路径path的时候,调用fsend命令处理程序。
在命令处理程序中,主要做了两件事:
FIL RecvFile;
YMODEMINFO YmodemDev;
FilePtr = (u8 *)argv[1];
YmodemRes = Ymodem_RecvFile ( &YmodemDev, &RecvFile ); //提供Ymodem信息和文件结构体,这个函数将处理与超级终端通信过程,并将接收到的数据通过文件系统写入指定位置。
2、Ymodem_RecvFile流程设计
与超级终端通过Ymodem协议处理的过程借用了状态机编程的思路。并在一个循环中处理通信过程。
状态机分三种状态:通信起始阶段、数据传输阶段、结束传输状态。
while (1){
switch ( YmodemState ){
case YStart: { //通信起始阶段
Uart_PutChar(MODEM_C); //发起始信号
StartChar = Uart_GetCharInTime(100,&Error);//ucos是每秒100个tick,100表示1s,每次等待1s钟。发生超时重发“C”
if ( Error == OS_ERR_TIMEOUT ){
WaitTime++;
if ( WaitTime >=60 )return 1; //等待60s,超时退出。
continue; //否则继续,发C。
}
if ( StartChar == MODEM_SOH ){
TempChar = Uart_GetChar();
if ( TempChar != 0x00 )return 3; // 不是00序号。
TempChar = Uart_GetChar();
if ( TempChar != 0xFF )return 3; // 不是00序号补码。
for ( i="0"; i<128; i++ ){ //接收数据包0,共128字节
YmodemDev->UserBuf = Uart_GetChar();
}
TempChar = Uart_GetChar();
TempChar = Uart_GetChar(); //丢弃CRC校验,暂时不想实现。
Uart_PutChar(MODEM_ACK); //发送确认信号。
GetNameAndSize( YmodemDev ); //调用子程序,通过缓冲区里的数据获取文件名字符串,以及长度字符串并转换为数据类型。
res = f_open( RecvFile, (const char *)YmodemDev->FileName, FA_READ | FA_WRITE | FA_CREATE_NEW );
YmodemState=YDataTrans;//状态切换到数据传输状态
YmodemDev->NextPackNum = 1;
Uart_PutChar ( MODEM_C ); //再发一个C,正式启动数据传输 }
}
break;
case YDataTrans: { //数据传输阶段
StartChar = Uart_GetChar();
switch ( StartChar ){
case MODEM_EOT: {
YmodemState = YEOT;
Uart_PutChar ( MODEM_ACK );
continue;
}
case MODEM_SOH:{
YmodemDev->PackLen = 128;
}
break;
case MODEM_STX:{
YmodemDev->PackLen = 1024;
}
break;
case MODEM_CANCEL:{
f_unlink(YmodemDev->FileName );
Uart_PutString("用户取消文件传输!\r\n");
return 2;
}
} //end of switch (StartChar)
TempChar = Uart_GetChar();
TempChar = Uart_GetChar();
for ( i="0"; i< YmodemDev->PackLen; i++ ){ //接收整个数据包
YmodemDev->UserBuf = Uart_GetChar();
}
TempChar = Uart_GetChar();
TempChar = Uart_GetChar(); //丢弃CRC校验,暂时不想实现。
if ( ErrorNum == 0 ){
res=f_write ( RecvFile, (void *)YmodemDev->UserBuf, YmodemDev->PackLen, &ByteWrite );//将接收到得数据写入文件
YmodemDev->NextPackNum++;
Uart_PutChar ( MODEM_ACK );
} //end of if (ErrorNum == 0 )
else {
Uart_PutChar ( MODEM_NAK ); //接收发现错误,要求重发。 }
}
break;
case YEOT: { //结束传输阶段
Uart_PutChar(MODEM_C);
StartChar = Uart_GetChar(); //接收起始字符。
if ( StartChar == MODEM_SOH ){
TempChar = Uart_GetChar();
if ( TempChar != 0x00 )return 3; // 不是00序号。
TempChar = Uart_GetChar();
if ( TempChar != 0xFF )return 3; // 不是00序号补码。
for ( i="0"; i<128; i++ ){
YmodemDev->UserBuf = Uart_GetChar();
}
TempChar = Uart_GetChar();
TempChar = Uart_GetChar(); //丢弃CRC校验,暂时不想实现。
Uart_PutChar(MODEM_ACK); //发送确认信号。
f_lseek ( RecvFile, YmodemDev->FileSize );
f_truncate ( RecvFile );
f_close( RecvFile );
return 0;
}
}
default:
return 0x20;
} //end of switch ( YmodemState );
} //end of while (1)
}
二、收获与体会
1、基于状态机的编程
这是一个很好的编程思路,我前面在按键扫描时用过,对消抖、按下时间累积、双击判定等的处理都有比较好的处理方法。
通过此次Ymodem协议的处理过程,我发现这种思路用于通信协议处理也能很好的处理通信的各个阶段的变迁、每个阶段下对于不同输入的反应。
我这里的处理是基于在状态下处理触发信号,对应不同的动作过程。还有一种编程机思路是基于触发信号产生不同的动作过程,然后在动作过程中根据当时的状态进行对应的处理。一种称为“横写状态机”,一种称为“竖写状态机”,以后还要多学习学习。
2、调试过程的体会
一个通信过程是否成功完成,取决于双方的协调动作,要很好的理解容易发生错误的地方。以及错误发生以后如何正确的定位。
在本次编写过程中,由于刚开始没有注意错误定位的问题。导致每次通信失败后,找不到具体哪里发生了错误。只得又把代码读一遍,修修补补,还是没有找到问题。进行单步调试后,也没有找到根本原因所在。
刚开始推测可能是“串口消息队列”太小,导致溢出,把队列加的很大,耗尽了我的RAM,仍然没有解决。再把本处理任务的堆栈改为较大,结果还是一样。
后来我在每一个可能检测到错误的地方,都加上了这样的话。
Uart_PutString("具体错误原因!\r\n");
很快就发现了,是文件系统写入错位,返回代码01,代表磁盘错误,再一跟踪很快发现是在磁盘连续写入的时候、写入多个扇区的函数出问题了。我也没有详细的去处理这个函数,而只是将写多个扇区的任务交给单个扇区的函数来完成。结果通信很快就成功了。写入一个<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />1M的文件需要2分30秒左右,也就是每秒接近7000字节,还算比较快了。
以下是通信成功的截图:
<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />
用户1573670 2013-6-25 11:53
用户1321739 2013-5-29 23:47
用户377235 2012-11-23 17:26