nix.long 于 2017-08-14
重新整理在csdn
STM32/Dvm项目移植FatFS时,使用W25Qxx Flash做文件系统,不能使用USB或其他外设接口直接进行文件拷贝, 因此移植Ymodem协议用于向Flash下载文件.
Ymodem在嵌入式项目中,常用于文件下载和IAP在线编程.
Ymodem协议是一种发送并等待的协议。即发送方发送一个数据包以后,都要等待接收方的确认,分为Ymodem-1K和Ymodem-G两种子格式.
一般情况使用YModem-1K传输,而平时所说的YModem也是指的是YModem-1k.
传输信号定义:
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只针对数据体计算,不包含帧头
SOH 00 FF filename 00 filezise 00 NUL[00] CRCH CRCL
- 传输起始帧以SOH信号开始,数据体长度128
- 序号为0,数据体已filename开始,为ascii码格式字符串,然后紧跟00表示文件名结束;之后紧跟filesize,将每位转换为16进制的ascii码,后续到数据体结尾完全补00。如文件名123.c 长度 1024字节(0x400), 那么在数据体的格式为: 31 32 33 2E 63 00 34 30 30 00 … 后续用00 补充
- filename和filesize构成最长不超过128字节
SIG CC ~CC Data[128]:[1024] <1A...> CRCH CRCL
在实际传输中, 数据帧根据文件大小不同分多种情况处理。假设文件大小为X.M=X/1024,N=X%1024.因为Ymodem-1K只有128和1024两种数据帧大小:
- 如果M大于0:那么这M帧按照每帧1024字节传输,SIG为信号STX
- 如果N大于128小于1024:SIG为STX,这帧按照1024字节传输,不足1024字节部分使用 1A 补全
- 如果N小于等于128:SIG为SOH,这帧按照128字节传输,不足128字节使用1A补全
SOH 00 FF 128x[00] CRCH CRCL
结束帧类似起始帧,序号为00, 但数据体为空(全0x00).
- 帧长恒为133(SOH开始)或1029(STX开始)之一
- 起始/结束帧序号一定为0x00(反码0xff)
- 数据帧序号从1开始计数, 但超过255后,会从0开始重新计数
- 发送方实际发送的数据size是大于等于实际文件大小的, 接收方善用filesize和1A进行实际size接收
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后,整个传输会话结束
这里没有根据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协议简介