近期有一个需求就是为Arduino开发板做一个基于蓝牙的无线烧录程序。眼下的Arduino程序都是通过USB线连接到电脑的主机上,实际的传输过程是基于USB协议的,这个过程还是比較麻烦的。由于每次的编译完以后都须要通过一个USB线来完毕传输烧录的工作,这个还是非常麻烦的。
原理解读在Arduino中。利用USB来完毕传输烧录大概是这么一个过程。
每一个Arduino源程序。即sketch文件,经过一系列的编译处理以后。终于会形成一个Intel HEX格式的文件。这个HEX文件事实上就一个被封装好的数据包,包括头部、长度、偏移量、数据类型、数据和校验和等6个字段。
再利用USB协议传输这个数据包,再通过板子上已有的bootloader程序把这个HEX文件里的有效字节码写到板子上的Flash存储器中。就是由于这个bootloader程序的存在。才使得多数的单片机具有片内引导程序自编程功能。MCU通过执行这个常驻Flash的bootloader程序。就能够利用不论什么可用的数据接口读代替码(注:在Arduino中,这里的代码是指HEX文件里的有效数据字段,是终于字节码的形式)后写入到自身的flash存储器中,从而实现了自编程的功能。
理解了上面的原理以后,你比方说我要做一个支持无线传输的类似bootloader的东西。无线传输採用蓝牙协议。
蓝牙的接收端将接收到的数据所有存在串口中,所以核心的技术除了蓝牙收发部分以及对flash存储器的读写和存储空间的管理以外,还须要实现对这个HEX文件的解析,更准确、形象地说应该是对这些数据包的解析。怎么把这个数据包拆开,取出有效的字节码数据,然后把这些字节码按顺序分块组装,写到flash存储器。再把PC指针指到開始的地方就能够了。只是这东西确实非常底层,就是在直接跟内存打交道。怎么管理内存。怎么读写内存,怎么拆分组装数据,出错了以后怎么擦除已经写的内容,怎样优化等等一些列问题都须要解决。
事非经过不知难。经过方知难与易。慢慢做吧!先来第一步。对HEX格式的文件进行解析。解析之前自然要弄懂HEX文件的详细格式。Intel HEX格式的文件是由多条记录组成,而每条记录又是由6个字段组成。这些记录是由一些代表机器语言代码和常量的16进制数据组成的。Intel HEX 文件经常使用来传输要存储在 ROM 或者 EPROM 中的程序和数据。大部分的 EPROM 编程器能使用 Intel HEX文件记录的基本格式例如以下:
RecordMark |
RecordLength |
LoadOffset |
RecordType |
Data |
Checksum |
记录标志 |
记录长度 |
装载偏移 |
记录类型 |
数据 |
校验和 |
当中,RecordMark字段事实上就是每条记录的首部,其值为0x3A,在ASCII码中就是冒号“:”。该字段在HEX文件里,这个头部仅仅占有一个字节。RecordLength表示每条记录包括的数据的长度,以字节为单位。最大描写叙述255个字节,表现为2个16进制的字符,该字段在HEX文件里占2个字节。
LoadOffset表示该记录中的数据在整个存取器空间中的偏移,用4个十六进制字符描写叙述一个16位数据。在HEX文件里该字段占有4个字节。
RecordType表示记录类型。表现为2个十六进制字符。取值有下面几种:
00表示数据记录。
01表示文件结束记录;
02描写叙述拓展段地址记录。
03描写叙述開始段地址记录。
04描写叙述扩展线性地址记录;
05描写叙述開始线性地址记录。
Data字段表示数据的详细内容,描写叙述方法仍是两个16进制的字符表示1字节的数据。此字段的长度由该记录的RecordLength决定,数据的解释取决于记录类型(RecordType)。
Checksum字段为校验和。这个校验和是这么来的。将RecordMark(“:”)后的全部的数据按字节相加,即成对相加起来,然后模除256得到余数,再对这个余数求补码,终于得出的结果就是校验和。所以检測方法也非常easy:在每一条记录内,将RecordMark(“:”)后的全部数据(包含Checksum)按字节相加后得到的8位数据为0,则说明数据无误。否则说明出错了。
至于什么是拓展段地址记录、開始段地址记录、扩展线性地址记录、開始线性地址记录这里不做具体的介绍。在芯艺的《AVR单片机GCC程序设计》的附录部分有具体的说明。而在Arduino的HEX文件里,记录类型仅仅有两种。数据记录和文件结束记录。所以RecordType这个字段的值不是0x00就是0x01。数据记录适用于8位、16位和32位格式,其具体格式例如以下:
记录名 |
RecordMark |
RecordLength |
LoadOffset |
RecordType |
Data |
Checksum |
记录标志 |
记录长度 |
装载偏移 |
记录类型 |
数据 |
校验和 |
|
内容 |
“:” |
X |
- |
“00” |
- |
- |
字节数 |
1 |
1 |
2 |
1 |
X |
1 |
文件结束记录适用于8位、16位和32位格式。其具体格式例如以下:
记录名 |
RecordMark |
RecordLength |
LoadOffset |
RecordType |
Checksum |
记录标志 |
记录长度 |
装载偏移 |
记录类型 |
校验和 |
|
内容 |
“:” |
“00” |
“0000” |
“01” |
“FF” |
字节数 |
1 |
1 |
2 |
1 |
1 |
比方说。有例如以下一条数据记录:“:1001A000808184608083808182608083808181609F”,则,其RecordMark为“:”,RecordLength为”10”。这里是16进制的,相应10进制为16,也就是说Data字段有16个字节;LoadOffset的值为01A0,也就是说在该条记录中,数据字段在内存中的起始地址为01A0;RecordType为00,表示是记录类型;Data的值为80818460808380818260808380818160,一共同拥有16个字节;Checksum的值为9F。用计算器依照上面的方式验证一下是能够得到一个低8位为0的数据,也就是说这条记录是合法的。
以下開始编码实现对HEX文件的解析。
HEX文件解析的实现解析过程还是比較简单的。这里直接附上源代码。
HexLexer.h头文件里定义了相关的类的属性和操作。
#ifndef _HEXLEXER_H_ #define _HEXLEXER_H_ #include <cstdio> #include <cstring> #include <cstdlib> /* Intel Hex文件解析器V1.0 Hex文件的格式例如以下: RecordMark RecordLength LoadOffset RecordType Data Checksum 在Intel Hex文件里,RecordMark规定为“:” */ #pragma warning(disable:4996) #define MAX_BUFFER_SIZE 43 class Hex { public: Hex(char mark); ~Hex(); void ParseHex(char *data);//解析Hex文件 void ParseRecord(char ch);//解析每一条记录 size_t GetRecordLength();//获取记录长度 char GetRecordMark();//获取记录标识 char *GetLoadOffset();//获取内存装载偏移 char *GetRecordType();//获取记录类型 char *GetData();//获取数据 char *GetChecksum();//获取校验和 private: char m_cBuffer[MAX_BUFFER_SIZE];//存储待解析的记录 char m_cRecordMark;//记录标识 size_t m_nRecordLength;//记录长度 char *m_pLoadOffset;//装载偏移 char *m_pRecordType;//记录类型 char *m_pData;//数据字段 char *m_pChecksum;//校验和 bool m_bRecvStatus;//接收状态标识 //size_t m_nIndex;//缓存的字符索引值 }; Hex::Hex(char mark) { this->m_cRecordMark = mark; m_cBuffer[0] = '\0'; //m_pBuffer = NULL; m_nRecordLength = 0; m_pLoadOffset = NULL; m_pRecordType = NULL; m_pData = NULL; m_pChecksum = NULL; m_bRecvStatus = false; //m_nIndex = 0; } Hex::~Hex() { delete m_pLoadOffset, m_pRecordType, m_pData, m_pChecksum; } #endif
HexLexer.cpp文件实现了头文件里函数,并给出了測试用例。
#include "HexLexer.h" #include <iostream> using namespace std; //获取记录标识 char Hex::GetRecordMark() { return this->m_cRecordMark; } //获取每条记录的长度 size_t Hex::GetRecordLength() { //char *len = (char*)malloc(sizeof(char)* 3); if (strlen(m_cBuffer)>=2) { char len[3]; len[0] = m_cBuffer[0]; len[1] = m_cBuffer[1]; len[2] = '\0'; char *p = NULL; return strtol(len, &p, 16); } else { return 0; } } //获取装载偏移 char* Hex::GetLoadOffset() { if (strlen(m_cBuffer) == (GetRecordLength() + 5) * 2) { char *offset = (char*)malloc(sizeof(char)* 5); for (int i = 0; i < 4; ++i) { offset[i] = m_cBuffer[i + 2]; } offset[4] = '\0'; m_pLoadOffset = offset; offset = NULL; } return m_pLoadOffset; } //获取记录类型 char* Hex::GetRecordType() { if (strlen(m_cBuffer) == (GetRecordLength() + 5) * 2) { char *type=(char*)malloc(sizeof(char)*3); type[0] = m_cBuffer[6]; type[1] = m_cBuffer[7]; type[2] = '\0'; m_pRecordType = type; type = NULL; } return m_pRecordType; } //获取数据 char* Hex::GetData() { if (strlen(m_cBuffer) == (GetRecordLength() + 5) * 2) { int len = GetRecordLength(); char *data = (char*)malloc(sizeof(char)*(len * 2 + 1)); for (int i = 0; i < len * 2;++i) { data[i] = m_cBuffer[i + 8]; } data[len * 2] = '\0'; m_pData = data; data = NULL; } return m_pData; } //获取校验和 char* Hex::GetChecksum() { int len = GetRecordLength(); if (strlen(m_cBuffer) == (len + 5) * 2) { char *checksum=(char*)malloc(sizeof(char)*3); checksum[0] = m_cBuffer[(len + 5) * 2 - 2]; checksum[1] = m_cBuffer[(len + 5) * 2-1]; checksum[2] = '\0'; m_pChecksum = checksum; checksum=NULL; } return m_pChecksum; } //解析Hex文件里的每一条记录 void Hex::ParseRecord(char ch) { size_t buf_len = strlen(m_cBuffer); if (GetRecordMark()==ch) { m_bRecvStatus = true; m_cBuffer[0] = '\0'; //m_nIndex = 0; return; } if ((buf_len==(GetRecordLength()+5)*2-1)) { //接收最后一个字符 m_cBuffer[buf_len] = ch; m_cBuffer[buf_len + 1] = '\0'; //检验接收的数据 char temp[3]; char *p = NULL; long int checksum = 0; for (int i = 0; i < strlen(m_cBuffer);i+=2) { temp[0] = m_cBuffer[i]; temp[1] = m_cBuffer[i + 1]; temp[2] = '\0'; checksum += strtol(temp, &p, 16); temp[0] = '\0'; } checksum &= 0x00ff;//取计算结果的低8位 if (checksum==0)//checksum为0说明接收的数据无误 { cout << "RecordMark " << GetRecordMark() << endl; cout << "RecordLength " << GetRecordLength() << endl; cout << "LoadOffset " << GetLoadOffset() << endl; cout << "RecordType " << GetRecordType() << endl; cout << "Data " << GetData() << endl; cout << "Checksum " << GetChecksum() << endl; } else//否则接收数据有误 { cout << "Error!" << endl; } m_cBuffer[0] = '\0'; m_bRecvStatus = false; m_nRecordLength = 0; m_pLoadOffset = NULL; m_pRecordType = NULL; m_pChecksum = NULL; m_bRecvStatus = false; } else if (m_bRecvStatus) { m_cBuffer[buf_len] = ch; m_cBuffer[buf_len + 1] = '\0'; //m_nIndex++; } } //解析Hex文件 void Hex::ParseHex(char *data) { for (int i = 0; i < strlen(data);++i) { ParseRecord(data[i]); } } int main(int argc, char *argv[]) { freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); Hex hex(':'); char ch; while (cin>>ch) { hex.ParseRecord(ch); } fclose(stdout); fclose(stdin); return 0; }
in.txt是在Arduino IDE编译blink程序生成的blik.cpp.hex的原始内容,直接复制到记事本中,其内容例如以下:
:100000000C945C000C946E000C946E000C946E00CA :100010000C946E000C946E000C946E000C946E00A8 :100020000C946E000C946E000C946E000C946E0098 :100030000C946E000C946E000C946E000C946E0088 :100040000C9488000C946E000C946E000C946E005E :100050000C946E000C946E000C946E000C946E0068 :100060000C946E000C946E00000000080002010069 :100070000003040700000000000000000102040863 :100080001020408001020408102001020408102002 :10009000040404040404040402020202020203032E :1000A0000303030300000000250028002B000000CC :1000B0000000240027002A0011241FBECFEFD8E043 :1000C000DEBFCDBF21E0A0E0B1E001C01D92A930AC :1000D000B207E1F70E94F1010C9401020C940000B8 :1000E00061E08DE00C94810161E08DE00E94BA0135 :1000F00068EE73E080E090E00E94F50060E08DE043 :100100000E94BA0168EE73E080E090E00C94F50084 :100110001F920F920FB60F9211242F933F938F933C :100120009F93AF93BF938091010190910201A091A1 :100130000301B09104013091000123E0230F2D371A :1001400020F40196A11DB11D05C026E8230F0296DB :10015000A11DB11D20930001809301019093020124 :10016000A0930301B09304018091050190910601D1 :10017000A0910701B09108010196A11DB11D8093C6 :10018000050190930601A0930701B0930801BF9168 :10019000AF919F918F913F912F910F900FBE0F9034 :1001A0001F9018953FB7F894809105019091060132 :1001B000A0910701B091080126B5A89B05C02F3F6B :1001C00019F00196A11DB11D3FBF6627782F892F19 :1001D0009A2F620F711D811D911D42E0660F771FDE :1001E000881F991F4A95D1F70895CF92DF92EF9219 :1001F000FF92CF93DF936B017C010E94D200EB0151 :10020000C114D104E104F10489F00E9400020E94AB :10021000D2006C1B7D0B683E734090F381E0C81ADE :10022000D108E108F108C851DC4FEACFDF91CF9146 :10023000FF90EF90DF90CF900895789484B582601E :1002400084BD84B5816084BD85B5826085BD85B57A :10025000816085BDEEE6F0E0808181608083E1E829 :10026000F0E0108280818260808380818160808361 :10027000E0E8F0E0808181608083E1EBF0E0808164 :1002800084608083E0EBF0E0808181608083EAE736 :10029000F0E08081846080838081826080838081BF :1002A000816080838081806880831092C10008957E :1002B000833081F028F4813099F08230A1F00895E4 :1002C0008730A9F08830B9F08430D1F48091800073 :1002D0008F7D03C0809180008F7780938000089588 :1002E00084B58F7702C084B58F7D84BD08958091D9 :1002F000B0008F7703C08091B0008F7D8093B000F5 :100300000895CF93DF9390E0FC01E458FF4F2491D0 :10031000FC01E057FF4F8491882349F190E0880F5A :10032000991FFC01E255FF4FA591B4918C559F4F49 :10033000FC01C591D4919FB7611108C0F8948C91CC :10034000209582238C93888182230AC0623051F4E5 :10035000F8948C91322F309583238C938881822B53 :10036000888304C0F8948C91822B8C939FBFDF917B :10037000CF9108950F931F93CF93DF931F92CDB723 :10038000DEB7282F30E0F901E859FF4F8491F901D9 :10039000E458FF4F1491F901E057FF4F04910023F7 :1003A000C9F0882321F069830E9458016981E02FF8 :1003B000F0E0EE0FFF1FEC55FF4FA591B4919FB7F2 :1003C000F8948C91611103C01095812301C0812B99 :1003D0008C939FBF0F90DF91CF911F910F91089544 :1003E00008950E941D010E94F0010E947000C0E06B :1003F000D0E00E9474002097E1F30E940000F9CF42 :060400000895F894FFCFFF :00000001FF
解析的结果直接写入到记事本中。结果例如以下:
RecordMark : RecordLength 16 LoadOffset 0000 RecordType 00 Data 0C945C000C946E000C946E000C946E00 Checksum CA RecordMark : RecordLength 16 LoadOffset 0010 RecordType 00 Data 0C946E000C946E000C946E000C946E00 Checksum A8 RecordMark : RecordLength 16 LoadOffset 0020 RecordType 00 Data 0C946E000C946E000C946E000C946E00 Checksum 98 RecordMark : RecordLength 16 LoadOffset 0030 RecordType 00 Data 0C946E000C946E000C946E000C946E00 Checksum 88 RecordMark : RecordLength 16 LoadOffset 0040 RecordType 00 Data 0C9488000C946E000C946E000C946E00 Checksum 5E RecordMark : RecordLength 16 LoadOffset 0050 RecordType 00 Data 0C946E000C946E000C946E000C946E00 Checksum 68 RecordMark : RecordLength 16 LoadOffset 0060 RecordType 00 Data 0C946E000C946E000000000800020100 Checksum 69 RecordMark : RecordLength 16 LoadOffset 0070 RecordType 00 Data 00030407000000000000000001020408 Checksum 63 RecordMark : RecordLength 16 LoadOffset 0080 RecordType 00 Data 10204080010204081020010204081020 Checksum 02 RecordMark : RecordLength 16 LoadOffset 0090 RecordType 00 Data 04040404040404040202020202020303 Checksum 2E RecordMark : RecordLength 16 LoadOffset 00A0 RecordType 00 Data 0303030300000000250028002B000000 Checksum CC RecordMark : RecordLength 16 LoadOffset 00B0 RecordType 00 Data 0000240027002A0011241FBECFEFD8E0 Checksum 43 RecordMark : RecordLength 16 LoadOffset 00C0 RecordType 00 Data DEBFCDBF21E0A0E0B1E001C01D92A930 Checksum AC RecordMark : RecordLength 16 LoadOffset 00D0 RecordType 00 Data B207E1F70E94F1010C9401020C940000 Checksum B8 RecordMark : RecordLength 16 LoadOffset 00E0 RecordType 00 Data 61E08DE00C94810161E08DE00E94BA01 Checksum 35 RecordMark : RecordLength 16 LoadOffset 00F0 RecordType 00 Data 68EE73E080E090E00E94F50060E08DE0 Checksum 43 RecordMark : RecordLength 16 LoadOffset 0100 RecordType 00 Data 0E94BA0168EE73E080E090E00C94F500 Checksum 84 RecordMark : RecordLength 16 LoadOffset 0110 RecordType 00 Data 1F920F920FB60F9211242F933F938F93 Checksum 3C RecordMark : RecordLength 16 LoadOffset 0120 RecordType 00 Data 9F93AF93BF938091010190910201A091 Checksum A1 RecordMark : RecordLength 16 LoadOffset 0130 RecordType 00 Data 0301B09104013091000123E0230F2D37 Checksum 1A RecordMark : RecordLength 16 LoadOffset 0140 RecordType 00 Data 20F40196A11DB11D05C026E8230F0296 Checksum DB RecordMark : RecordLength 16 LoadOffset 0150 RecordType 00 Data A11DB11D209300018093010190930201 Checksum 24 RecordMark : RecordLength 16 LoadOffset 0160 RecordType 00 Data A0930301B09304018091050190910601 Checksum D1 RecordMark : RecordLength 16 LoadOffset 0170 RecordType 00 Data A0910701B09108010196A11DB11D8093 Checksum C6 RecordMark : RecordLength 16 LoadOffset 0180 RecordType 00 Data 050190930601A0930701B0930801BF91 Checksum 68 RecordMark : RecordLength 16 LoadOffset 0190 RecordType 00 Data AF919F918F913F912F910F900FBE0F90 Checksum 34 RecordMark : RecordLength 16 LoadOffset 01A0 RecordType 00 Data 1F9018953FB7F8948091050190910601 Checksum 32 RecordMark : RecordLength 16 LoadOffset 01B0 RecordType 00 Data A0910701B091080126B5A89B05C02F3F Checksum 6B RecordMark : RecordLength 16 LoadOffset 01C0 RecordType 00 Data 19F00196A11DB11D3FBF6627782F892F Checksum 19 RecordMark : RecordLength 16 LoadOffset 01D0 RecordType 00 Data 9A2F620F711D811D911D42E0660F771F Checksum DE RecordMark : RecordLength 16 LoadOffset 01E0 RecordType 00 Data 881F991F4A95D1F70895CF92DF92EF92 Checksum 19 RecordMark : RecordLength 16 LoadOffset 01F0 RecordType 00 Data FF92CF93DF936B017C010E94D200EB01 Checksum 51 RecordMark : RecordLength 16 LoadOffset 0200 RecordType 00 Data C114D104E104F10489F00E9400020E94 Checksum AB RecordMark : RecordLength 16 LoadOffset 0210 RecordType 00 Data D2006C1B7D0B683E734090F381E0C81A Checksum DE RecordMark : RecordLength 16 LoadOffset 0220 RecordType 00 Data D108E108F108C851DC4FEACFDF91CF91 Checksum 46 RecordMark : RecordLength 16 LoadOffset 0230 RecordType 00 Data FF90EF90DF90CF900895789484B58260 Checksum 1E RecordMark : RecordLength 16 LoadOffset 0240 RecordType 00 Data 84BD84B5816084BD85B5826085BD85B5 Checksum 7A RecordMark : RecordLength 16 LoadOffset 0250 RecordType 00 Data 816085BDEEE6F0E0808181608083E1E8 Checksum 29 RecordMark : RecordLength 16 LoadOffset 0260 RecordType 00 Data F0E01082808182608083808181608083 Checksum 61 RecordMark : RecordLength 16 LoadOffset 0270 RecordType 00 Data E0E8F0E0808181608083E1EBF0E08081 Checksum 64 RecordMark : RecordLength 16 LoadOffset 0280 RecordType 00 Data 84608083E0EBF0E0808181608083EAE7 Checksum 36 RecordMark : RecordLength 16 LoadOffset 0290 RecordType 00 Data F0E08081846080838081826080838081 Checksum BF RecordMark : RecordLength 16 LoadOffset 02A0 RecordType 00 Data 816080838081806880831092C1000895 Checksum 7E RecordMark : RecordLength 16 LoadOffset 02B0 RecordType 00 Data 833081F028F4813099F08230A1F00895 Checksum E4 RecordMark : RecordLength 16 LoadOffset 02C0 RecordType 00 Data 8730A9F08830B9F08430D1F480918000 Checksum 73 RecordMark : RecordLength 16 LoadOffset 02D0 RecordType 00 Data 8F7D03C0809180008F77809380000895 Checksum 88 RecordMark : RecordLength 16 LoadOffset 02E0 RecordType 00 Data 84B58F7702C084B58F7D84BD08958091 Checksum D9 RecordMark : RecordLength 16 LoadOffset 02F0 RecordType 00 Data B0008F7703C08091B0008F7D8093B000 Checksum F5 RecordMark : RecordLength 16 LoadOffset 0300 RecordType 00 Data 0895CF93DF9390E0FC01E458FF4F2491 Checksum D0 RecordMark : RecordLength 16 LoadOffset 0310 RecordType 00 Data FC01E057FF4F8491882349F190E0880F Checksum 5A RecordMark : RecordLength 16 LoadOffset 0320 RecordType 00 Data 991FFC01E255FF4FA591B4918C559F4F Checksum 49 RecordMark : RecordLength 16 LoadOffset 0330 RecordType 00 Data FC01C591D4919FB7611108C0F8948C91 Checksum CC RecordMark : RecordLength 16 LoadOffset 0340 RecordType 00 Data 209582238C93888182230AC0623051F4 Checksum E5 RecordMark : RecordLength 16 LoadOffset 0350 RecordType 00 Data F8948C91322F309583238C938881822B Checksum 53 RecordMark : RecordLength 16 LoadOffset 0360 RecordType 00 Data 888304C0F8948C91822B8C939FBFDF91 Checksum 7B RecordMark : RecordLength 16 LoadOffset 0370 RecordType 00 Data CF9108950F931F93CF93DF931F92CDB7 Checksum 23 RecordMark : RecordLength 16 LoadOffset 0380 RecordType 00 Data DEB7282F30E0F901E859FF4F8491F901 Checksum D9 RecordMark : RecordLength 16 LoadOffset 0390 RecordType 00 Data E458FF4F1491F901E057FF4F04910023 Checksum F7 RecordMark : RecordLength 16 LoadOffset 03A0 RecordType 00 Data C9F0882321F069830E9458016981E02F Checksum F8 RecordMark : RecordLength 16 LoadOffset 03B0 RecordType 00 Data F0E0EE0FFF1FEC55FF4FA591B4919FB7 Checksum F2 RecordMark : RecordLength 16 LoadOffset 03C0 RecordType 00 Data F8948C91611103C01095812301C0812B Checksum 99 RecordMark : RecordLength 16 LoadOffset 03D0 RecordType 00 Data 8C939FBF0F90DF91CF911F910F910895 Checksum 44 RecordMark : RecordLength 16 LoadOffset 03E0 RecordType 00 Data 08950E941D010E94F0010E947000C0E0 Checksum 6B RecordMark : RecordLength 16 LoadOffset 03F0 RecordType 00 Data D0E00E9474002097E1F30E940000F9CF Checksum 42 RecordMark : RecordLength 6 LoadOffset 0400 RecordType 00 Data 0895F894FFCF Checksum FF RecordMark : RecordLength 0 LoadOffset 0000 RecordType 01 Data Checksum FF參考文献