Ymodemプロトコルを用いたファイル転送ソフトを作成しました。Ymodemプロトコルは、バイナリ転送プロトコルの一種で、ファームウェアのソフトウェアオブジェクトとして実装され、Flashメモリなどに書き込むために使用される。ターミナルソフト「TeraTerm」にはYmodemプロトコルが実装されています。ソフトウェア開発環境にはEclipse(Juno)、ツールチェーンには「MinGW GCC」を使ってC言語で作成します。今回は、TeraTermのバージョン4.86を使用します。

使用するYmodemプロトコルは次の特徴を持ちます。、

  • 1024バイトもしくは128バイト単位でデータを転送する。
  • エラー検出に16ビットのCRC符号を利用する。
  • 転送エラーがあった場合、エラーのあったブロックを再送することができる。しかし指定したブロックからやり直す機能はない。
  • ファイル名やファイルサイズ、タイムスタンプなどのファイル情報を転送することができる。
  • 一度に複数のファイルを転送することができる(バッチ転送)。

今回は、パソコン上で動作するYmodemプトロコルソフトを作成します。確認はTeratermで行い、Ymodemプロトコルを使用して、Teratermから作成したファイル転送ソフトにバイナリファイルを転送し、ファイル転送ソフトでは、ファイル名「test.bin」に保存します。確認のために、転送するバイナリファイルとtest.binを比較します。

Ymodemプトロコルソフトでは次の関数を使用します。

Ymodem_Rcv関数
Teratermから受け取ったデータを解析した、渡されたファイル名やサイズを保存し、受け取ったヘッダを除くデータの部分だけをファイルに書き込みます。
Recv_Packet関数
受け取ったパケットの種別を判断し、パケットのサイズやYmodemの終了などを判断します

Ymodem_Rcv関数

最初に受け取ったパケットにはファイル名とファイルサイズが含まれています。これらを一時保存し、ここでこれから送られているバイナリファイルのデータを保存するファイル「test.bin」をオープンします。次のパケット以降バイナリファイルがパケットに分割されて送られてきます。あらかじめオープンしておいたtest.binに、Teratermから受け取ったパケットのヘッダの部分を除いたデータを順次書き込みます。
受信したデータにエラーがないことを確認した後にACKを送出し、エラーを検出するとNAKを送出します。また、ファイルのオープンもしくは書き込みに失敗したときはCANを送出します。

int32_t Ymodem_Rcv(uint8_t *buf) {
	uint8_t packet_data[PACKET_1K_SIZE + PACKET_OVERHEAD], file_size[FILE_SIZE_LENGTH], *file_ptr, *buf_ptr;
	int32_t i, packet_length, session_done, file_done, packets_received, errors, session_begin, size = 0;

	for (session_done = 0, errors = 0, session_begin = 0;;) {
		for (packets_received = 0, file_done = 0, buf_ptr = buf;;) {
			switch (Recv_Packet(packet_data, &packet_length, NAK_TIMEOUT)) {
			case 0:
				errors = 0;
				switch (packet_length) {
				/* Abort by sender */
				case -1:
					Send_Byte(ACK);
					return 0;
					/* End of transmission */
				case 0:
					Send_Byte(ACK);
					file_done = 1;
					break;
					/* Normal packet */
				default:
					if ((packet_data[PACKET_SEQNO_INDEX] & 0xff)
							!= (packets_received & 0xff)) {
						Send_Byte(NAK);
					} else {
						if (packets_received == 0) {
							/* Filename packet */
							if (packet_data[PACKET_HEADER] != 0) {
								/* Filename packet has valid data */
								for (i = 0, file_ptr = packet_data+ PACKET_HEADER;(*file_ptr != 0) && (i < FILE_NAME_LENGTH);) {
									FileName[i++] = *file_ptr++;
								}
								FileName[i++] = '\0';
								for (i = 0, file_ptr++; (*file_ptr != ' ') && (i < FILE_SIZE_LENGTH);) {
									file_size[i++] = *file_ptr++;
								}
								file_size[i++] = '\0';
								size = atoi(file_size);

								fp = fopen(fname, "wb");
								if (fp == NULL ) {
									Send_Byte(CA);
									Send_Byte(CA);
									return -1;
								} else {
									Send_Byte(ACK);
									Send_Byte(CRC16);

								}
							}
							/* Filename packet is empty, end session */
							else {
								Send_Byte(ACK);
								file_done = 1;
								session_done = 1;
								break;
							}
						}
						/* Data packet */
						else {
							fwrite(packet_data + PACKET_HEADER, sizeof(unsigned char), packet_length, fp);
							Send_Byte(ACK);
						}
						packets_received++;
						session_begin = 1;
					}
				}
				break;
			case 1:
				Send_Byte(CA);
				Send_Byte(CA);
				return -3;
			default:
				if (session_begin > 0) {
					errors++;
				}
				if (errors > MAX_ERRORS) {
					Send_Byte(CA);
					Send_Byte(CA);
					return 0;
				}
				Send_Byte(CRC16);
				break;
			}
			if (file_done != 0) {
				break;
			}
		}
		if (session_done != 0) {
			break;
		}
	}
	return (int32_t) size;
}

Recv_Packet関数

Ymodem_Rcv関数から呼び出され、受け取ったパケットに従って、パケットサイズ、EOT、CAN、アボートを判断し、その結果をYmodem_Rcv関数に戻します。

static int32_t Recv_Packet(uint8_t *data, int32_t *length, uint32_t timeout) {
	uint16_t i, packet_size;
	uint8_t c;
	*length = 0;
	if (Rcv_Byte(&c, timeout) != 0) {
		return -1;
	}
	switch (c) {
	case SOH:
		packet_size = PACKET_SIZE;
		break;
	case STX:
		packet_size = PACKET_1K_SIZE;
		break;
	case EOT:
		return 0;
	case CA:
		if ((Rcv_Byte(&c, timeout) == 0) && (c == CA)) {
			*length = -1;
			return 0;
		} else {
			return -1;
		}
	case ABORT1:
	case ABORT2:
		return 1;
	default:
		return -1;
	}
	*data = c;
	for (i = 1; i < (packet_size + PACKET_OVERHEAD); i ++) {
		if (Rcv_Byte(data + i, timeout) != 0) {
			return -1;
		}
	}
	if (data[PACKET_SEQNO_INDEX] != ((data[PACKET_SEQNO_COMP_INDEX] ^ 0xff) & 0xff)) {
		return -1;
	}
	*length = packet_size;
	return 0;
}

Ymodemで使用するパケットの最初のヘッダデータには次の1バイトデータが使用されます。各バイトデータに対応する定義とその意味を次に示します。

#define SOH     (0x01)  /* start of 128-byte data packet */
#define STX     (0x02)  /* start of 1024-byte data packet */
#define EOT     (0x04)  /* end of transmission */
#define ACK     (0x06)  /* acknowledge */
#define NAK     (0x15)  /* negative acknowledge */
#define CA      (0x18)  /* two of these in succession aborts transfer */
#define CRC16      (0x43)  /* 'C' == 0x43, request 16-bit CRC */
#define ABORT1     (0x41)  /* 'A' == 0x41, abort by user */
#define ABORT2     (0x61)  /* 'a' == 0x61, abort by user */

サンプルプログラムの作成

作成した関数を呼び出して、バイナリファイルを転送するサンプルプログラムを次に示します。

int main() {
	uint8_t Number[10] = "          ";
	int32_t Size = 0;

	int bdrate = 9600; /* 9600 baud */
	char mode[] = { '8', 'N', '1', 0 };

	if (RS232_OpenComport(cport_nr, bdrate, mode)) {
		printf("Can not open comport\n");
		return (0);
	}

	RS232_cputs(cport_nr, "\r\nStart to Transfer.\r\n");

	SerialPut(
			"Waiting for the Downloadt file.\n\r");
	Size = Ymodem_Rcv(&tab_1024[0]);
	if (Size > 0) {
		SerialPut(	"\n\n\r Programming Completed Successfully!\n\r--------------------------------\r\n Name: ");
		SerialPut(FileName);
		itoa(Size, Number, 10);
		SerialPut("\n\r Size: ");
		SerialPut(Number);
		SerialPut(" Bytes\r\n");
		SerialPut("-------------------\n");
	} else {
		SerialPut("\n\rFailed to transfer!\n\r");
	}
	fclose(fp);
	return  (0);

}

サンプルプログラムの実行

TeraTerm上で作成したサンプルプログラムを実行した結果を次に示します。
サンプルプログラムがバイナリファイルを転送し終わった時の状態を次に示します。 

バイナリファイルの転送完了

サンプルプログラムが、転送されたファイル名とそのサイズをTeraTermに表示した画面を次に示します。 
 
転送されたファイル名とそのサイズの表示

TeraTermによるYmodemプロトコルのログの取り方

TeraTermを使用すると、Ymodemプロトコルのログが取得できます。TeraTermのインストール先のフォルダに置かれるTERATERM.INIファイルをエディタで開くと、YMODEプロトコルMのログを取得するか否かの設定ができます。次に示すように「; YMODEM log」項目の「YmodemLog」を「on」にするだけです。

TeratermによるYmodemログの取り方

TeraTermのTERATERM.INIファイルでYmodemプロトコルのログを指定すると、次のディレクトリにYmodemのログファイル「YMODEM.LOG」ファイルが作成されます。毎回転送するごとに書き換えられます。

C:\Users\ne\AppData\Local\VirtualStore\Program Files\teraterm

実際に取得したTeraTermのYmodemプロトコルのログを次に示します。

YMODEM Send start: Wed Apr 29 11:16:25 2015

<<<
43                                                  C

>>>
01 00 FF 6C 69 63 65 6E 73 65 2E 74 78 74 00 36     ...license.txt.6
37 39 35 20 31 32 34 37 34 33 33 36 37 31 30 20     795 12474336710 
31 30 30 36 34 34 00 00 00 00 00 00 00 00 00 00     100644..........
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 2C 6A                                      ...,j

<<<
06 43                                               .C

>>>
02 01 FE 4C 69 63 65 6E 73 65 20 6F 66 20 54 65     ...License of Te
72 61 20 54 65 72 6D 0D 0A 0D 0A 43 6F 70 79 72     ra Term....Copyr
69 67 68 74 20 28 63 29 20 54 2E 54 65 72 61 6E     ight (c) T.Teran
69 73 68 69 2E 0D 0A 43 6F 70 79 72 69 67 68 74     ishi...Copyright
20 28 63 29 20 54 65 72 61 54 65 72 6D 20 50 72      (c) TeraTerm Pr
6F 6A 65 63 74 2E 0D 0A 41 6C 6C 20 72 69 67 68     oject...All righ
74 73 20 72 65 73 65 72 76 65 64 2E 0D 0A 0D 0A     ts reserved.....
52 65 64 69 73 74 72 69 62 75 74 69 6F 6E 20 61     Redistribution a
6E 64 20 75 73 65 20 69 6E 20 73 6F 75 72 63 65     nd use in source
20 61 6E 64 20 62 69 6E 61 72 79 20 66 6F 72 6D      and binary form
73 2C 20 77 69 74 68 20 6F 72 20 77 69 74 68 6F     s, with or witho
75 74 20 6D 6F 64 69 66 69 63 61 74 69 6F 6E 2C     ut modification,
0D 0A 61 72 65 20 70 65 72 6D 69 74 74 65 64 20     ..are permitted 
70 72 6F 76 69 64 65 64 20 74 68 61 74 20 74 68     provided that th
65 20 66 6F 6C 6C 6F 77 69 6E 67 20 63 6F 6E 64     e following cond