近期有一个需求就是为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文件的详细格式。
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] = '