标签归档:dsPIC33E

Microchip公司16位单片机dsPIC33E/PIC24E系列bootloader的开发(5)

Bootloader必须百分百可靠。换句话说,bootloader永远是一个无名英雄,躲在芯片的flash里面,在必要时发挥其作用。它不能在任何时候被自己擦除或者覆盖。然而,如果你了解单片机bootloader的实现机制,你不难发现bootloader说到底也只是一段存储在flash空间里的二进制代码而已,与你其他的应用程序无二。因此,bootloader是可以被擦除或者覆盖的。一个设计优秀的bootloader可以避免绝大部分的自毁式错误,但却无法避免由于硬件异常而造成的此类错误。尽管硬件异常五花八门,有不可预测性。但我们任然可以通过一定的软件机制来保护bootloader免遭损坏。

举例来说,当单片机闪存第0页被擦除后,却由于某些原因(比如IO错误,跳电等)紧跟其后的自我重编程操作无法完成,那单片机的bootloader就丢失了位于闪存最前端最重要的跳转重置(GOTO-RESET)命令行。造成的结果就是在单片机冷启动以后无法正确找到bootloader,从而“挂死”。如果你不是很明白为什么单片机闪存0页0行对bootloader的重要意义,为什么这么重要的闪存内容必须每次都要擦除并重新编程,请阅读我本系列文章的第一篇

为了避免类似上述硬件异常所造成的bootloader损坏,我在这里设计了一种“回滚”机制,保存bootloader免遭丢失GOTO-RESET指令的危险。简而言之,一个用于“回滚”的覆盖标识变量用来监控单片机闪存中的GOTO-RESET指令是否被擦除。在每次执行bootloading之前,回滚机制首先将GOTO-RESET中的指令保存在另外一个安全的地方,然后执行擦除。一旦发生了擦除操作,覆盖标识变量就被设置为1,表明单片机原有的GOTO-RESET已被存在另外一个地方,该指令位置已被清空。而后续程序一旦发现正常的bootloader无法完成,而该变量值却任然为1,那就启动“回滚”,即将保存在他处的原GOTO-RESET指令重新写回到原来位置。那尽管这次bootloader失败了,但单片机任然可以正常启动,继续在必要的时候执行bootloader工作。显而易见,该标识变量只有在一次bootloader顺利完成之后才会被重置为0,表明无须对GOTO-RESET进行回滚。

        .bss
bt_Addr:.space 6

; 两个GOTO指令(6字节)用于指向bootloader的起始地址
; 该6字节指令位于闪存第0页第0行

下面是我在单片机中实现的回滚机制代码,注释在代码中间。你当然可以用不用的方式来实现

Roll_Bk:
; 读入写锁存
mov     #0xFA, W0
mov     W0, TBLPAG
mov     #0, W0

; 缓存变量bt_Addr已经保存了GOTO-RESET指令内容。
; 是“抢”在该指令被删除之前保存下来的
mov     #bt_Addr, W1
tblwth.b    [W1++], [W0]
tblwtl.b    [W1++], [W0++]
tblwtl.b    [W1++], [W0++]
tblwth.b    [W1++], [W0]
tblwtl.b    [W1++], [W0++]
tblwtl.b    [W1], [W0]

; 将bt_Addr中的内容写入地址0.
; 导入地址
mov     #0, W0
; W0此时是0,即GOTO-RESET的地址是0,位于闪存的最前端。
mov     W0, NVMADRU
mov     W0, NVMADR
; 赋值NVMCON使其开始编程。
mov     #0x4001, W0
rcall   Write
return

Microchip公司16位单片机dsPIC33E/PIC24E系列bootloader的开发(4)

如果你看过本系列文章的上一篇关于如何从编译好的二进制固件里提取数据,那你距离最后的成功——通过bootloader给单片机下载固件——仅一步之遥了。

与ICD编程器利用芯片的PCG/PCD管脚下载程序不同,Microchip在其16位单片机芯片里实现了一整套指令/寄存器专门用于对自己的flash闪存进行读写。比如:

  • NVMADRU(高8位地址)和NVMADR(低16位地址)是记录flash程序空间地址的寄存器,他们所保存的24位地址指向用户所需要读/写/擦除固件程序的起始地址。
  • NVMCON寄存器中的NVMOP字节用于触发不同的flash空间操作,通过不同的设置执行比如批量擦除、页擦除、行擦除等。
  • TBLPAG寄存器用于保存”Write Latches”写锁存(下面有关于这个写锁存的讨论)24位地址中的高8位。对于dsPIC33E或者PIC24E的芯片,你只需要将“0xFA”赋值给这个TBLPAG寄存器,因为从下图中可以看出,“Write Latches”物理地址被硬实现从0xFA0000开始。
    BL4_Write_Latches_Address
  • TBLWTHTBLWTL指令用于将RAM中的数据“锁”入Write Latches中,然后才能执行最终的固件程序“烧”入闪存步骤。
  • 还有很多……

考虑到Microchip在其芯片上实现了“行编程”的功能,所以以“行”为单位(即每128个指令大小为单位)进行编程就显得比较方便而且快捷。“Write Latches”(写锁存)可以被理解为一个在Flash(高8位地址是0xFA)上面的临时数据存储器,用于临时存放这128个指令数据,等待最终的“烧制”过程。在行编程模式中,Microchip的disPIC33E系列似乎不支持直接将RAM中的数据直接写入用户闪存程序存储区,所有数据必须先进入RAM,然后从RAM中导入写锁存,最终再从写锁存写入目标地址的Flash中。下面是一段源代码展示了这一过程:

  1. 通过外围模块(peripheral,可以是UART、I2C、SPI、PMP、普通I/O等)接收固件二进制数据,并存入RAM:
            .bss
            buffer:  .space (#128*3)      ;在RAM内存中创建128×3字节大小的数组
            ;通过UART/SPI/I2C接受外面传进来的二进制代码,这里详细不列举
    
    
  2. 将RAM中的数据“锁”如写锁存“Write Latches”:
    
            mov       #128, W1      ;要“烧”128个指令,即一个“行”的大小
            mov       #0xFA, W0
            mov       W0, TBLPAG    ;上面提到的,Write Latches的地址总是从0xFA0000开始
            mov       #0, W2        ;Write Latches的低16位地址从0开始
            mov       #buffer, W3   ;上面创建的128×3大小的buffer的地址
    WRLA:   tblwth.b  [W3++], [W2]
            tblwtl.b  [W3++], [W2++]
            tblwtl.b  [W3++], [W2++]
            dec       W1, W1
            bra       nz, WRLA
    
    
  3. 执行“行”编程(row program):
            ;在开始最终的编程步骤之前,芯片必须知道这128个指令写到用户程序存储区的哪里。
            ;所以我么必须对此进行定义。
            ;假定W0寄存器里已经导入了地址的高8位
            mov       W0, NVMADRU    ;将这高8位导入NVMADRU寄存器
            ;假定W0现在导入的是地址的低16位
            mov       W0, NVMADR     ;现在将低16为导入NVMADR寄存器
            ;现在芯片知道写锁存里面128个指令应该写到flash的什么位置
            ;一些就绪就待最终的执行了!
            mov       #0x4002, W0
            mov       W0, NVMCON
            mov       #0x55, W0
            mov       W0, NVMKEY
            mov       #0xAA, W0
            mov       W0, NVMKEY
            bset      NVMCON, #WR
            nop
            nop
            ;等待写入操作完成。
    WWTF:   btsc    NVMCON, #WR
            bra     WWTF
    
    
  4. 完成了一个“行编程”(即128个指令),重复步骤1~3直到完成整个芯片的编程。

使用dsPIC33E系列单片机可复用IO(remappable)的注意事项

相比Microchip公司早期的16位单片机,dsPIC33E系列的一大新特色是可复用重映射的I/O引脚。这为电路板的设计提供了极大的灵活性,但同时也略微增加了编程的难度。下面就说一些使用心得:

  • 引脚若被标记成“RPn”则说明该管脚既可以被映射成输出也可以被映射成输入类型的I/O;而“RPIn”则只能被用于输入类型。在设计电路板的时候,需要特别留意,不要将“RPIn”用在了输出类型上。
  • 输出管脚的定义代码与输入类型不同。比如,如果将RPI16用于UART的接受器(Receiver)引脚,则程序里要这样定义:
    // U1ART RX connects to RPI16
    _U1RXR = 16;
    

    而如果要把RP118用在UART1的发送(transmitter)引脚上,你需要反过来定义:

    // RP118 connects to peripheral 0b00001
    _RP118R = 1; // 0b00001 stands for UART1
    

    我觉得这应该和芯片内部的Look-up table具体实现方式有关。

  • 单单定义了某一个管脚用在什么功能上还不够!请注意,尤其是当该引脚还和模拟口(通常是模数转换,ADC,标记为”ANx“)共用的时候,一定要特别设置将该模拟口功能关闭。不知为何原因Microchip对于这些引脚的默认功能为模拟口,除非特地设置ANSELx为零,不然无法使用其复用的数字电路功能。比如,如果希望使用下图中的3号管脚(同时也是AN29,RE5和RP85)将其映射到任何外设模块(如UART,DCI,SPI,I2C等等),必须在程序中声明:
    // ANSxy, x stands for Port A/B/C/D/E/..., y stands for number
    _ANSE5 = 0; // Enables digital port pin
    

    dspic33e_remappable_peripheral_io

    [2013年11月26日追加以下内容]
    拥有模拟功能的I/O口不仅仅局限于数模转换口,还包括运算放大器端口以及比较器端口,比如上图中RG6、RG7同时还具有C1IN3-、C1IN1-,在初始化后默认为模拟口。为了能使用其数字端口功能,则必须清空其相应的ANSxy寄存器(这个例子_ANSG6、_ANSG7)。我在一个使用DCI模块的项目里始终无法在其CSDI输入口上获得数据之后才注意到这个问题。因为CSDI被重映射到了一个比较器的输入口上,在没有清空ANSELx寄存器的情况下,该口上返回的值永远为零。一旦将其相应的ANSxy清空以后,数据马上就进入了DCI的接收模块里。

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代码用来抽出相关数据以及按照地址重新编排。

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))

在本系列的第一篇文章里, 我提到了GOTO-RESET的指令必须用你指向bootloader开头地址的自定义数据替换。这样,一个被修改过GOTO-RESET指令的单片机在 上电开机以后就直奔bootloader,而不是单片机默认的用户程序起始地址。如果bootloader发现有新的固件程序达到,则将其烧入自己的闪存 并执行;如果没有,则执行闪存里已有的程序。

所以这里有两个步骤来处理GOTO-RESET问题:1、将编译器生成的GOTO-RESET数据提取出来并暂时保存好,然后用你自定义的 bootloader起始地址数据填入;2、拿出前面暂存好的原始GOTO-RESET数据放在bootloader的末尾,这样bootloader结 束之后,单片机将会开始执行用户程序。说白了,就是在正常的启动顺序中间插入一个bootloader过程,所以bootloader结束之后,还得将单 片机引回到正常执行用户程序的状态。

Microchip公司16位单片机dsPIC33E/PIC24E系列bootloader的开发(2)

上一篇“Microchip公司16位单片机dsPIC33/PIC24E系列bootloader的开发(1)”文章中提到了bootloader一些基本知识以及不同设计架构之间的比较,最终选择了把bootloader置于闪存空间尾部的方案。这篇文章我们要讨论一下bootloader的工作流程,让你了解一下整套bootloader所涉及的方方面面。

一般来说,设计完你的单片机程序以后,编译器会将你的C/C++或者汇编代码编译成二进制代码,通常保存在“.hex”结尾的文件里。这个hex文 件遵循Intel Hex32格式,我会在稍后细谈。然后你需要打开编程器的PC端软件,读入hex文件,确保编程器(Microchip的话是ICD2或者ICD3)已正 确连好,然后二进制代码就通过芯片上的PGC/PGD口写入单片机的闪存里。在这个流程里,Microchip已经帮我们做好了其中所有的工具链,分别 是:编译器(MPLAB IDE X)- 编程器PC端软件(MPLAB IPE X)- 编程器(ICD2或者ICD3) – 单片机接收端(PGC/PGD口)。在bootloader的流程了,你就得自己来开发其中的一些组件了,比如你可以继续使用MPLAB IDE X做为编译器,但是你得有自己开发的INTEL HEX32解析器,接下来就是你自己开发的硬件/软件负责将固件传到单片机上,有很多途径,可以是USB、UART、SPI、I2C甚至是SD卡,然后你 还要开发单片机上的接收/烧制代码,也就是真正的bootloader了。可见,所谓开发bootloader,其实是差不多整一个生态系统。

      1. 理解INTEL Hex32标准
        编译完单片机程序以后,编译器会生成一个hex后缀名的单片机固件。如果你用16进制文本编辑器打开这个文件,你会看到一行一行的16进制码。所有的这些16进制码都服从下面的格式:

         :BBAAAATTHHHH... ...HHHHCC
        

        一个字母表示一个16进制数字(即一个字节byte),其中:

        • [:] 每一行开始比有一个分号,没有列外
        • [BB] HHHH… …HHHH中字节的长度
        • [AAAA] 该行数据在芯片闪存中的起始地址
        • [TT] 该行数据类型,其中
          • 00:数据
          • 01:结尾标志
          • 02:Extended Segment Address Record,片地址偏移
          • 03:片地址偏移起始标志
          • 04:Extended Linear Address Record,块地址偏移
          • 05:块地址偏移起始标志
        • [HHHH… …HHHH] 数据部分
        • [CC] CRC循环冗余校验码

        详细的INTEL HEX32标准细节就不在这里解释了,网上很容易找到。Mirochip的DS70619B文档:dsPIC33E/PIC24E闪存编程规范就是一个很 好的参考。接下来我会详细讲述一下INTEL HEX32在dsPIC33/PIC24E上的具体实现,总结一下散落在多个文档中的一些零星要点。让我们从一个具体的例子开始来加深你的理解:

        :020000040108EA
        :0200000212FFBD
        :0401000090FFAA5502
        :00000001FF
        
          • 块地址偏移0108
          • 片地址偏移12FF
          • 原起始地址0100
          • 计算真实地址:0108左移16位变成0108 0000,12FF左移4位变成0001 2FF0,原起始地址0000 0100,加上偏移量后得到真实地址为:0109 30F0
          • 最终可以将这段hex解释为
            地址 数据
            010930F0 90
            010930F1 FF
            010930F2 AA
            010930F3 55
            

            值得注意的是,一旦含有“02”或者“04”数据类型的16进制代码行出现,则之后的所有行的数据地址都需要偏移,直至出现新一个“02”或者“04”,那么就需要重新计算新的偏移量。

  1. INTEL HEX32在dsPIC33E/PIC24E上的具体实现
    Microchip在INTEL HEX32的基础上增加了一些自己的规则。要想正确地解析dsPIC33E/PIC24E的hex十六进制文件,就必须深入Microchip为数众多的文档才能窥探到庐山真面目,下面就是一些汇总:

        1. dsPIC33E/PIC24E的每个指令(instruction)占用3个字节(byte)或者24位(bit),与此相对应的,用户闪存程 序存储区也是同样的3个字节宽度。这里问题就来了,标准INTEL HEX32格式中,每个数据总是4字节宽度。Microchip为了遵循此标准,编译生成的hex文件中每个指令数据也是4个字节,只不过最右边的一个字 节总是0×00,被称为“phantom byte(虚拟字节)”,顾名思义,就是没有实际作用纯粹用来充数字节。所以上面提到的BB部分除以4就是该行十六进制码中所含指令的数量。
        2. 用户闪存程序存储区的地址是以2为递增单位,这是为了照顾上面所述的3个字节宽度的指令。可以这样理解,闪存构造是以两个字节为一最小单位,3个 字节的指令将占去两个闪存地址空间,尽管有一个字节是“phantom byte”。有这个“phantom byte”也不意味着浪费了一个字节,因为这个字节在闪存里没有实现,只是为了照顾INTEL HEX32标准而凑数的。可以从Microchip的DS70609C_CN文档中得到验证“24位闪存程序存储器可被视为并排放置的两个16位空间,每 个空间都有相同的地址范围……由于闪存程序存储器只有24位宽,因此TBLRDH和TBLWTH指令能寻址闪存程序存储器中并不存在的最高字节。该字节被 称为“虚拟字节”。同时这也解释了dsPIC33EP512MU810的用户闪存存储区的地址上限为0x557EE(即十进制350,206)却只能存储 175,104(即350,206的一半)个指令了。
        3. INTEL HEX32文件中的“AAAA”部分表示的是该行数据的起始地址,但是需要除以2才是真正相对应芯片闪存的实际地址。
        4. 理解GOTO和RESET指令 Microchip编译好的Intel Hex32文件中,第二行一定是GOTO和RESET指令。该指令在芯片重置或者启动的时候被访问,告知应该从哪里开始读取用户程序。这样的其实地址为 “0”,只含有两个指令即8个字节(实际有用字节为6,2各位虚拟字节)。第一个指令总是以0×04结尾(不包括虚拟字节),表示所指向地址的低16位, 前两个字节就是改16位的地址。第二个指令总是以0×00结尾,表示所指向地址的高16位,同样的第一第二个字节就是该16位的地址。下面是一个例子:
          :0800000000a80400020000004a
          :08 0000 00 00a80400 02000000 4a
          (1)(2)  (3)(4)      (5)      (6)
          

          (1)这一行有8个字节数据
          (2)起始地位为0
          (3)类型为数据
          (4)第一个指令:00 a8 04 00(虚拟字节),可以解释为:指向低16位为0xa800的地址
          (5)第二个指令:02 00 00 00(虚拟字节),可以解释为:指向高16位为0×0002的地址
          (6)CRC循环冗余校正码
          这一行综合起来可以解释为,跳转到0x02a800地址。再来一个例子:

           :080000000002040000000000f2
          

          这一行的意思是,跳转到0×200地址。(这是一个dsPIC33FJ256GP710A芯片正常编译后的实际hex文件,是指在启动后跳向用户程 序的第一行。dsPIC33F的一个储存页为512个指令大小,0×200即跳过第一个含有GOTO/RESET以及中断向量表的储存页。不同于 dsPIC33F,我们讨论的dsPIC33E系列的储存页大小升级到了1024个指令,这个我会在后面一篇文章里提到。)

Microchip公司16位单片机dsPIC33E/PIC24E系列bootloader的开发(1)

Mikael Gustafsson开 发的bootloader支持Microchip公司8位/16位/32位单片机全系列产品,只可惜到了最新70MHz的dsPIC33E/PIC24E 系列以后,就不再开源,并且开始收费了。如果你决定自己开发一款用在dsPIC33E/PIC24E单片机上的bootloader,我的这系列篇文章将 会给您提供非常好的参考。首先,让我从剖析Mikael Gustafsson的“ds30 Loader”入手。

  1. Bootloader入门:
    Bootloader可以被成为自加载程序,简而言之就是自己对自己编程。bootloader本身就是一段存储在单片机flash中的固件。但不同的 是,bootloader只做一件固定的事情——接受用户的应用代码固件,并将其存放在空余的flash中。除此以外,bootloader不做其他任何 事情,甚至可以被视作不存在。一个含有bootloader的单片机运行起来和不含bootloader、直接通过编程器烧制固件的单片几乎完全一样。对 于bootloader而言,体积小是起码的要求,因为bootloader多少占用你的闪存空间。Bootloader的另一大优势是免去了必须要编程 器烧制固件的麻烦,你只需要第一次用编程器烧制bootloader,之后就可以通过bootloader来烧制新的固件了。当然这也对 bootloader的可靠性提出了非常高的要求——bootloader要保证不会把自己给删除了。
  2. bootloader放在哪里:
    网上对于bootloader的架构有很多不同的见解。“ds30 Loader”这类的产品将bootloader放在闪存区的尾部,而Microchip给出的设计参考“AN1094 – Bootloader for dsPIC30F/33F and PIC24F/24H Devices”则选择紧接着中断向量表的闪存开始部分存放bootloader。我个人倾向于“ds30 Loader”的架构,因为使用闪存尾部的做法无须应用固件开发工程师对开发环境(IDE)进行特殊配置。

    BL1_no_bootloader_cn

    图1. 不含bootloader的单片机闪存使用情况。编译器从最开头可用的空间用起。当芯片启动的时候GOTO指令将引导系统读取用户应用程序。

    BL1_microchip_bootloader_cn

    图2.将bootloader至于闪存头部的设计架构。严格地说,bootloader并非是置于最最前部。因为“GOTO指令”和“RESET地 址”以及中断向量表(IVT,interrupt vector table)处于闪存的最前部,接下来才是用户可用空间。一般而言,这种设计将bootloader放在第二个编程区(program block / erase block)中。因此编译器必须通过配置知道第二编程区已经被占用需要空出。这种设计的另一个缺点是第一编程区(即包含GOTO,RESET以及IVT的 区)未被使用的部分也被浪费了。因为flash在擦除的时候是按照区位做小单位的。第一区有GOTO,RESET和IVT,不得不必须跳过,在接下来的第 二区放置bootloader。同样的道理,第二区有空余也不能被其他程序使用。(注,IVT后面空出来的一区其余闪存可以通过修改复杂的编译器设置来使 用,这里不详述)

    BL1_my_preferred_bootloader_cn

    图3. 将bootloader置于闪存尾部的设计架构。这样的话,编译器按照寻常的做法,从头开始使用flash空间,无需特别配置。当然,对于被 bootloader占用的那块编程区余下部分,则依旧只能浪费。当然你要确保你的应用程序不至于太长(绝大部分情况下是不会发生的)而覆盖掉位于尾端的 bootloader。

至于如何将bootloader烧制到flash中,没办法,这时候你只能用一次编程器,不过你只需要这次用一下它,接下来bootloader就开始帮你烧制固件了。

不要离开,我的下一篇文章将和你深入探讨理解Microchip单片机的二进制hex代码规范