Ymodem协议移植

nix.long 于 2017-08-14
重新整理在csdn

文章目录

    • 0.Ymodem介绍
    • 1. Ymodem的协议格式
      • 1.0 帧格式
      • 1.1 传输起始帧
      • 1.2 数据帧
      • 1.3 结束帧
      • 1.4 要点
    • 2. Ymodem的传输过程
    • 3. Ymodem向STM32的移植

STM32/Dvm项目移植FatFS时,使用W25Qxx Flash做文件系统,不能使用USB或其他外设接口直接进行文件拷贝, 因此移植Ymodem协议用于向Flash下载文件.

0.Ymodem介绍

Ymodem在嵌入式项目中,常用于文件下载和IAP在线编程.
Ymodem协议是一种发送并等待的协议。即发送方发送一个数据包以后,都要等待接收方的确认,分为Ymodem-1K和Ymodem-G两种子格式.

  • YModem-1K用1024字节信息块传输取代标准的128字节传输,数据的发送回使用CRC校验,保证数据传输的正确性。它每传输一个信息块数据时,就会等待接收端回应ACK信号,接收到回应后,才会继续传输下一个信息块,保证数据已经全部接收。
  • YModem-g传输形式与YModem-1K差不多,但是它去掉了数据的CRC校验码,同时在发送完一个数据块信息后,它不会等待接收端的ACK信号,而直接传输下一个数据块。正是它没有涉及错误校验,才使得它的传输速度比YModem-1K来得块。

一般情况使用YModem-1K传输,而平时所说的YModem也是指的是YModem-1k.


1. Ymodem的协议格式

传输信号定义:

1.0 帧格式

START CC ~CC Data[128]:[1024] CRCH CRCL

一个Ymodem数据帧由帧头(START CC ~CC)、数据体和帧尾三部分构成:

1) START : 如果是SOH,表示这帧数据体长度为128 Bytes; 为STX, 标识这帧数据体长度为1024 Bytes.(总长恒为128+5 或者1024+5)
2) CC : 表示帧序号, 从0开始增加,超过255后,又从0开始计数
3) ~CC为: CC的反码. 一起进行帧序的有效性判断
4) CRCH/CRCL分别为CRC16校验的高低字节, 校验多项式定义为: CRC16-CCITT/x16+x12+x5+1. CRC只针对数据体计算,不包含帧头

1.1 传输起始帧

SOH  00 FF  filename 00 filezise 00  NUL[00] CRCH CRCL
  1. 传输起始帧以SOH信号开始,数据体长度128
  2. 序号为0,数据体已filename开始,为ascii码格式字符串,然后紧跟00表示文件名结束;之后紧跟filesize,将每位转换为16进制的ascii码,后续到数据体结尾完全补00。如文件名123.c 长度 1024字节(0x400), 那么在数据体的格式为: 31 32 33 2E 63 00 34 30 30 00 … 后续用00 补充
  3. filename和filesize构成最长不超过128字节

1.2 数据帧

SIG  CC ~CC Data[128]:[1024] <1A...> CRCH CRCL

在实际传输中, 数据帧根据文件大小不同分多种情况处理。假设文件大小为X.M=X/1024,N=X%1024.因为Ymodem-1K只有128和1024两种数据帧大小:

  1. 如果M大于0:那么这M帧按照每帧1024字节传输,SIG为信号STX
  2. 如果N大于128小于1024:SIG为STX,这帧按照1024字节传输,不足1024字节部分使用 1A 补全
  3. 如果N小于等于128:SIG为SOH,这帧按照128字节传输,不足128字节使用1A补全

1.3 结束帧

SOH  00 FF  128x[00] CRCH CRCL

结束帧类似起始帧,序号为00, 但数据体为空(全0x00).


1.4 要点

  1. 帧长恒为133(SOH开始)或1029(STX开始)之一
  2. 起始/结束帧序号一定为0x00(反码0xff)
  3. 数据帧序号从1开始计数, 但超过255后,会从0开始重新计数
  4. 发送方实际发送的数据size是大于等于实际文件大小的, 接收方善用filesize和1A进行实际size接收

2. Ymodem的传输过程

 1. 接收方发送信号C启动传输会话,然后进入等待(SOH)状态,如果没有回应,就会超时退出
 2. 发送方开始时处于等待C过程中。收到C以后,发送携带文件名和文件长度的起始帧(SOH)数据包开始信号。进入等待(ACK)状态
 3. 接收方收到SOH起始帧以后,CRC校验满足,则发送ACK。发送方接收到ACK,又进入等待“文件传输开启”信号,即重新进入等待“C”的状态
 4. 接收方发送C,表示可以开始数据的传输
 5. 于是发送方发送数据帧、接收方接收到后回复ACK,如此循环进行数据接收(过程中双方因为任何异常,如人工终端、通讯故障等都可能造成传输中断).
 6. 文件传输完毕后,发送方发送EOT信号,接收方收到后,回应NAK
 7. 发送方再次发送EOT,接收方回应ACK。
 8. 接收方发送C,准备再次文件传输
 9. 如果是单次文件传输,发送方发送传输结束帧,接收方回应ACK后,整个传输会话结束

3. Ymodem向STM32的移植

这里没有根据Ymodem协议进行全新实现,而是选用了ST官方 STM32F0xx_IAP demo的一个Ymodem实现进行修改移植. 它原本用于IAP的, 这里修改为即可适用普通文件传输也可以用于IAP.

3.1. 原ST代码下载 原代码在对filesize处理时存在bug,将最后一帧多余的1A也认作文件内容了
3.2. 重构代码为两部分:ANSI-C部分和Porting移植层,ANSI-C部分可以方便移植到各个平台,而只需要重新实现Porting层的接口即可
3.3 重构后的目录结构:

src: 为ANSI-C部分, 方便向各平台移植
inc: porting头文件,包含需要移植实现的接口函数

3.4 接口函数


/*********************************************************
 *  The  Porting APIs Should Be Implemented              
 ********************************************************/
extern unsigned int     SerialKeyPressed(unsigned char *key);
extern void             SerialPutChar(unsigned char c);
extern unsigned char    SerialReadByte(void);

extern void             ymodem_init(void);
extern unsigned int     ymodem_get_receive_maxsize(void);
extern unsigned int     ymodem_get_transmit_size(void);

//0 - success ; -1 - fail
extern int              ymodem_recv_start_cb(const char * filename, const unsigned int filesize);
extern int              ymodem_recv_processing_cb(const unsigned char * buffer, const unsigned int buff_size);
extern int              ymodem_recv_end_cb(void);

3.5 在项目的接口实现

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/

/********************************************************************************
 *                     Porting APIs
 ********************************************************************************/
typedef enum{
    TRANS_FILE  = 0,
    IAP_IMAGE   = 1,
}YMODEM_ACTION_E;

typedef struct{
    COMM_TYPE_E         ymodem_uart;
    YMODEM_ACTION_E     ymodem_action;
    uint32_t            ymodem_receive_supported_maxsize;
    uint32_t            ymodem_transmit_size;
    
    bool_t              ymodem_file_inited;
    uint32_t            ymodem_file_total_size;
    uint32_t            ymodem_file_handled_size;
    FIL                 ymodem_file_handle;
    uint8_t             ymodem_file_tmppath[FF_MAX_LFN];
}YMODEM_DATA_T;

STATIC YMODEM_DATA_T s_ymodem_data;


PUBLIC void ymodem_init(void){
    memset(&s_ymodem_data, 0, sizeof(YMODEM_DATA_T));
    
    s_ymodem_data.ymodem_uart = COM1;
    comm_enable_rxit(s_ymodem_data.ymodem_uart);
    
    s_ymodem_data.ymodem_action = TRANS_FILE;
    
    switch(s_ymodem_data.ymodem_action){
        case TRANS_FILE:
            s_ymodem_data.ymodem_receive_supported_maxsize = 1024 * 1024 *5;
            s_ymodem_data.ymodem_transmit_size = 0;
            break;
        
        case IAP_IMAGE:
            #warning "MUST implement GOT transmit SIZE!"   
            s_ymodem_data.ymodem_receive_supported_maxsize = 1024 * 1024 *5;
            s_ymodem_data.ymodem_transmit_size = 0;
            break;
        
        default:
            //Not supported
            break;
    }
}
PUBLIC uint32_t ymodem_get_receive_maxsize(void){
    return s_ymodem_data.ymodem_receive_supported_maxsize;
}

PUBLIC uint32_t ymodem_get_transmit_size(void){
    return s_ymodem_data.ymodem_transmit_size;
}

//0 - success ; -1 - fail
PUBLIC int ymodem_recv_start_cb(const char * filename, const uint32_t filesize){
    FRESULT fres;
    const char * prefix = (const char*)"1:/";
    char *filepath = (char*)&s_ymodem_data.ymodem_file_tmppath[0];
    uint32_t filepath_size = sizeof(s_ymodem_data.ymodem_file_tmppath);
    
    switch(s_ymodem_data.ymodem_action){
        case TRANS_FILE:
            if(strlen(prefix) + strlen((char*)filename) >= filepath_size){
                return -1;      //path length too long
            }
            memset(&s_ymodem_data.ymodem_file_tmppath[0], 0, filepath_size);
            filepath = strcat((char*)filepath, prefix);
            filepath = strcat((char*)filepath, (char*)filename);
            
            if(FR_OK != (fres = f_open (&s_ymodem_data.ymodem_file_handle, filepath, FA_WRITE | FA_CREATE_ALWAYS))){
                logd("Ymodem Start: create file(%s) fail(%d)...\n", filepath, fres);
                return -1;
            } else {
                logd("Ymodem Start: create file(%s) ok(%d)...\n", filepath, fres);
                
                s_ymodem_data.ymodem_file_inited = TRUE;
                s_ymodem_data.ymodem_file_total_size = filesize;
                s_ymodem_data.ymodem_file_handled_size = 0;
                return 0;
            }
            //break;
        
        case IAP_IMAGE:
            /* erase user application area */
            //FLASH_If_Erase(APPLICATION_ADDRESS);
            
            break;
        
        default:
            //Not supported
            break;
    }
    return -1;
}

PUBLIC int ymodem_recv_processing_cb(const uint8_t * buffer, const uint32_t buff_size){
    FRESULT fres;
    uint32_t to_write_size = 0;
    uint32_t writted_size = 0;
    uint32_t order = 0;
    
    switch(s_ymodem_data.ymodem_action){
        case TRANS_FILE:
            to_write_size = s_ymodem_data.ymodem_file_total_size - s_ymodem_data.ymodem_file_handled_size;
            to_write_size = (buff_size > to_write_size) ? to_write_size : buff_size;
            
            order = (uint32_t)buffer - 2;
            if(FR_OK != (fres = f_write(&s_ymodem_data.ymodem_file_handle, buffer, to_write_size, &writted_size))){
                logw("Ymodem process(%d): write file(%d - %d) fail(%d)...\n", *((uint8_t*)order), to_write_size, writted_size, fres);
                return -1;
            } else {
                s_ymodem_data.ymodem_file_handled_size += to_write_size;
                logi("Ymodem process(%d): write file(%d/%d) ok(%d)...\n", *((uint8_t*)order), s_ymodem_data.ymodem_file_handled_size, 
                            s_ymodem_data.ymodem_file_total_size, fres);
                return 0;
            }
            //break;
        
        case IAP_IMAGE:
            //if (FLASH_If_Write(&flashdestination, (uint32_t*) ramsource, (uint16_t) packet_length/4)  == 0)
            break;
        
        default:
            //Not supported
            break;
    }
    return -1;
}

PUBLIC int ymodem_recv_end_cb(void){
    FRESULT fres;
    FILINFO fno;

    switch(s_ymodem_data.ymodem_action){
        case TRANS_FILE:
            if(TRUE != s_ymodem_data.ymodem_file_inited)
                return -1;
            
            fres = f_close(&s_ymodem_data.ymodem_file_handle);
            logd("Ymodem End: close file res(%d)...\n", fres);
            
            fres = f_stat((const TCHAR *)s_ymodem_data.ymodem_file_tmppath, &fno);
            if(fres != RES_OK){
                logw("Get File Status Fail(%d)...\n", fres);
            } else {
                logi("Get File Status ok(%d), file size(%d) Bytes...\n", fres, fno.fsize);
            }
            
            memset(&s_ymodem_data.ymodem_file_handle, 0, sizeof(FIL));
            memset(&s_ymodem_data.ymodem_file_tmppath[0], 0, sizeof(s_ymodem_data.ymodem_file_tmppath));               
            s_ymodem_data.ymodem_file_total_size = 0;
            s_ymodem_data.ymodem_file_handled_size = 0;
            s_ymodem_data.ymodem_file_inited = FALSE;
            return 0;
            //break;
        
        case IAP_IMAGE:
            /* erase user application area */
            //FLASH_If_Erase(APPLICATION_ADDRESS);
            
            break;
        
        default:
            //Not supported
            break;
    }
    return -1;
}
/**
  * @brief  Test to see if a key has been pressed on the HyperTerminal
  * @param  key: The key pressed
  * @retval 1: Correct
  *         0: Error
  */
uint32_t SerialKeyPressed(uint8_t *key)
{
    uint8_t err;
    uint8_t ret;
    
    //wait 10ms at most
    ret = comm_read_char(s_ymodem_data.ymodem_uart, 2, &err);

    if(COMM_NO_ERR == err){
        *key = ret;
        return 1;
    } else {
        return 0;
    }
}
//fail - 0xff; success -other value
uint8_t SerialReadByte(void){
    uint8_t err;
    uint8_t ret;
    
    //wait 10ms at most
    ret = comm_read_char(s_ymodem_data.ymodem_uart, 10, &err);

    if(COMM_NO_ERR == err){
        return ret;
    } else {
        return 0xff;
    }
}

/**
  * @brief  Print a character on the HyperTerminal
  * @param  c: The character to be printed
  * @retval None
  */
void SerialPutChar(uint8_t c)
{
    comm_send_char(s_ymodem_data.ymodem_uart, c, 10);  
}


参考: YModem协议简介

你可能感兴趣的:(嵌入式)