Microchip公司16位单片机dsPIC33E/PIC24E系列bootloader的开发(3)
如果你读过我bootloader开发系列文章的第二篇, 你应该了解了如何从MPLAB编译好的hex二进制文件中抽取所需要信息,包括数据的地址以及其内容。从另一个角度看,hex文件其实就是一个对照表,告 诉你某一个数据应该放在单片机闪存的哪个部位。这篇文章将着重讲一下dsPIC33E/PIC24E的用户闪存程序存储区(user memory space)的结构、一些解析hex文件的技巧以及GOTO/RESET指令的替换。
在能够将二进制数据放入单片机的闪存程序存储区相对应的位置之前,我们必须充分了解单片机闪存的结构。dsPIC33E/PIC24E单片机的用户 闪存存储区是一块连续的存储空间,地址范围从0×000000到0x02ABFE(对大容量型号单片机而言是0x0557FE)。为了便于批量编程/擦 除,整一块空间可以以不同单位来分割:擦除页(erase block,page)大小为1024个指令;编程块(program block,row)大小为128个指令。换句话说,一个擦除页含有8个编程块,或者1页有8行。所以从擦除页或者编程块的角度来看,地址上限是 0x02ABFE的单片机有86个擦除页或者684个编程块;而对0x0557FE大小的单片机来说则是171个擦除页或者1368个编程块。另外,小容 量单片机地址上限是0x02ABFE,换成十进制那就意味着175,104个地址。如果你将175,104除以1024或者128,你应该获得相应的 171和1368,你可能会纳闷为什么我前面说小容量的单片是86个擦除页和684个编程块,才一半?如果你不明白其中的道理,你可以查看我上篇文章,我 提到了每个指令需要两个地址空间。你也许还会注意到,175,104除以2再除以1024是应该得到85.5而不是86,你算的没错,Microchip 给最后一个擦除页只做了一半大小,512个指令,不过仍旧称它为“一个”页而已。擦除页是删除程序的最小单位,意味着你一旦执行一个删除命令,则至少要删 除一个页,即1024个指令,当然单片机还有一个整体擦除器件命令,顾名思义,一擦整个芯片都擦掉。同样的,编程块是编程的最小单位,跑一个块编程指令则 会一下子写入128个指令,当然这也意味着你在开始执行块编程之前,这个128个指令必须准备就位。锁存器(latch media)就是在写入闪存之前用来临时存放这些指令的地方,我会在后面的文章里详细解释这个。
如果你读了我上一篇文章,二进制代码提取应该是相当简单明了的。下面是一段我写的Python代码用来抽出相关数据以及按照地址重新编排。
[sourcecode language=”python”]
def _Parse_Hex32(self):
extended_Lineaer_Address = 0 # Left-shift by 16 bits
extended_Segment_Address = 0 # Left-shift by 4 bits
for i in range(0, len(self._hex32_Lines)):
#print str(self._hex32_Lines[i])
byte_Count, starting_Address, record_Type, data = self._Parse_Line(self._hex32_Lines[i])
if record_Type == 1: # End record
if i != (len(self._hex32_Lines) – 1):
raise Hex32_Invalid("Data type "End(0)" appears (line "" + str(i+1) + "") before the end of file.")
elif record_Type == 2: # Extended segment address
if len(data) == 2:
print ("!!! Warning !!!: Data type "Extended Segment Address(2)" appears on line"" + str(i+1) + "". ")
extended_Segment_Address = (data[0] * 256 + data[1]) * 16
else:
raise Hex32_Invalid("Data type "Extended Segment Address(2)" (line"" + str(i+1) + "") contains more than two bytes.")
elif record_Type == 4: # Extended linear address
if len(data) == 2:
extended_Linear_Address = (data[0] * 256 + data[1]) * 256
else:
raise Hex32_Invalid("Data type "Extended Linear Address(4)" (line"" + str(i+1) + "") contains more than two bytes.")
elif record_Type == 0: # Data record
for i in range(0, len(data) / 4):
device_Address = (extended_Linear_Address + extended_Segment_Address + starting_Address) / 2 + i
# Flag the LUT, divide the address by because the real device addres inrements by 2
self._Flag_LUT(device_Address)
# Fill in the hex into the array
self._flash_Memory[device_Address * self._instruction_Size_In_Hex + 0] = data[self._instruction_Size_In_Hex * i + 0]
self._flash_Memory[device_Address * self._instruction_Size_In_Hex + 1] = data[self._instruction_Size_In_Hex * i + 1]
self._flash_Memory[device_Address * self._instruction_Size_In_Hex + 2] = data[self._instruction_Size_In_Hex * i + 2]
self._flash_Memory[device_Address * self._instruction_Size_In_Hex + 3] = data[self._instruction_Size_In_Hex * i + 3]
else:
raise Hex32_Invalid("Unsupport data type: " + str(reccord_Type))
[/sourcecode]
在本系列的第一篇文章里, 我提到了GOTO-RESET的指令必须用你指向bootloader开头地址的自定义数据替换。这样,一个被修改过GOTO-RESET指令的单片机在 上电开机以后就直奔bootloader,而不是单片机默认的用户程序起始地址。如果bootloader发现有新的固件程序达到,则将其烧入自己的闪存 并执行;如果没有,则执行闪存里已有的程序。
所以这里有两个步骤来处理GOTO-RESET问题:1、将编译器生成的GOTO-RESET数据提取出来并暂时保存好,然后用你自定义的 bootloader起始地址数据填入;2、拿出前面暂存好的原始GOTO-RESET数据放在bootloader的末尾,这样bootloader结 束之后,单片机将会开始执行用户程序。说白了,就是在正常的启动顺序中间插入一个bootloader过程,所以bootloader结束之后,还得将单 片机引回到正常执行用户程序的状态。
One Reply to “Microchip公司16位单片机dsPIC33E/PIC24E系列bootloader的开发(3)”