LPC11U3x系列IAP升级 BootLoader分析

   首先要明确LPC11Uxx系列的程序执行开始地址是0x00000000, 从0x0开始的512字节大小的存储空间存放的是中断向量表,之后便是程序,数据一般存放在8KB大小的SRAM中。(具体可参考LPC11U3X存储器映射表)

IAP升级主要分为两大部分:Bootloader程序和app程序。

  •     BootLoader程序主要通过USB与上位机进行通信,接收写/读MCU命令并作出相应的响应,在这里我将所有的操作都放在MAIN_RunToHID函数中,之后具体分析。  
  •     APP程序则是用户自己编写的需要进行运行的程序(我这里是闪灯的程序),APP程序最主要的是完成复制中断向量表以及跳转程序入口,同样地放在一个函数MAIN_RunToAPP中。

具体分析两部分程序的思路:

  1. MAIN_RunToHID

        首先进行USB的初始化工作以及连接USB,让PC机能将USB设备识别成HID设备,主要是对USB库的调用以及修改USB描述符(可参考USB-HID描述符配置)。

         在这个项目中,我将HID设备的端点传输方式设置为中断传输,采用端点4进行数据传输,中断传输适合HID这种传输数据量少的设备。在usbuser.c的端点4事件响应函数中添加对数据处理的代码。对于主机传输过来的数据,调用USB_ReadEP(HID_EP_OUT,OutReport)函数存储到OutReport[64]中;设备发给主机的数据,通过调用USB_WriteEP(HID_EP_IN,InReport)函数实现,数据预先存储在InReport[64]中。

      在端点4的IN、OUT事件响应中分别调用自己编写的函数GetInReport()、SetOutReport(),GetInReport()完成对InReport[64]中的数据进行修改,而SetOutReport()则是对收到的OutReport做处理。

      对SetOutReport()函数进行分析:

          SetOutReport()函数主要用于处理我定义的HID通信协议以及完成读/写MCU的操作 —— 当主机发送‘M’,即OutReport[0] == ‘M’时,即表示接下来应该开始进行写MCU的操作(实际就是将APP程序的bin文件写进事先设定好的地址中),执行完写操作时,修改InReport[0]的值以发送ACK响应给主机;当主机发送'F'时,即OutReport[0] == ‘F’时,表示接下来应该进行的是写外部Flash操作(这部分先保留)。

     写MCU(其实就是写单片机内部Flash)部分分析:

          主要参数

                 —— Write_MCU_Addr,写MCU的起始地址,我设置为0x00005000

                          0x00005000其实就是APP 程序的起始存放地址

                          0x00000000-0x00004FFF用于存放此BootLoader程序

                 —— Write_MCU_Size,APP程序bin文件的大小,我的文件大小1984个字节

                 —— Write_MCU_End,写MCU的结束地址加一个字节,0x000057C1

          Flash存储器有个特点,就是只能写0,不能写1。所以如果原来的地址有数据了,意味着有一些位为0,这些位就相当于无效了。所以写之前必须确保它们都为1,只有擦除后(其实就是写1)之后才可以进行写操作。另外每次擦除都必须擦除一个4K大小的扇区,这是flash的特性所决定的。

         根据写起始和写结束地址确定APP程序的大小有没有超过一个扇区的大小,再分别进行数据写操作,主要调用u32IAP_PrepareSectors和u32IAP_CopyRAMToFlash两个函数,IAP函数是固化在0x1FFF1FF1处的一个有传入参数和返回参数的一个函数,除需要写入数据的部分外其他都应该写上0xFF。

        注意事项:写Flash时有对齐操作

        __align(4) uint8_t write_buffer[256]={0};  //保证分配的数组空间4字节对齐,同时保证数组首地址可被4整除

   2、MAIN_RunToAPP 

        这部分程序主要做中断向量表映射的工作。我们知道,单片机的起始地址都存放有一张中断向量表,这张中断向量表存放了存放了几乎所有的中断程序的入口地址,当单片机使用到某个中断时,就会从表中取出其入口地址,强制PC指向该地址处,从而跳转到中断处理函数进行相应的中断处理。

      对于我们的APP程序同样的也有自己的中断向量表,但是由于我们在生成APP程序时设置了其开始运行地址为写MCU时的写起始地址(我这里设置的是0x00005000),因此它的中断向量表也相应的存放在0x00005000起始的512字节大小位置处(中断向量表的大小为512B)

       关于为什么要做中断向量表的重映射,以下作解释:

       首先,不管向量表是否有重映射,单片机复位后都是从0x0地址处获取堆栈指针、复位程序入口以及中断向量地址;

    在IAP升级中,通常包含有Boot和App两部分,Boot中的向量表地址是ROM地址,一般需要通过JTAG类似工具才能对齐进行改写。而用户的APP程序有自己的中断向量表(比如其程序入口地址、用到哪些中断以及其相应的中断处理函数地址),和Boot的中断向量表内容是不同的。

      为了保证运行APP程序时能跳到正确的中断处理函数地址处,就需要改变0x0处起始的中断向量表。而前面说过0x0处地址处的中断向量表地址是ROM地址,需要特殊的工具才能对其进行改写,这对于需要简单进行升级操作来说是很不方便的,因此就有中断映射这一概念。

      在LPC11Uxx用户直到手册中有这么一段:

     系统存储器重映射寄存器选择是从Boot ROM、闪存还是SRAM 读取异常向量。默认情况下,闪存映射到地址0x0000 0000。当SYSMEMREMAP 寄存器的MAP 位设为0x0 或0x1时, Boot ROM 或RAM 将分别映射到存储器映射(地址0x0000 0000 至0x0000 0200)的底部512 字节。

     0x0 引导加载程序模式。中断向量重映射到Boot ROM。
     0x1 用户RAM 模式。中断向量重映射到静态RAM。
     0x2 用户闪存模式。中断向量不会被重映射,一直位于闪存中。

   这就提供了一个改变0x0处中断向量表内容但又不需要对其进行重新擦除写入的方法:我们可以将APP程序起始地址开始的512字节内容先复制到SRAM(起始地址是0x10000000)中,然后设置重映射寄存器的值为0x1,让中断向量表重映射到RAM中,程序执行时就能取到APP程序的中断向量表了。实际Debug时也能看到,当进行重映射后,0x0处开始的512字节数据就跟RAM区开始的512字节数据一样了,这样就完成了中断向量表的映射。映射而不是对其擦写也能防止在升级过程中掉电导致boot也无法启动的情况。

    中断向量表映射完成后,就可以将程序的入口地址赋值给函数指针,进行APP程序的跳转。这里赋值和跳转到的语句如下:

       pMCU_APP_ENTRY app_entry;   

       app_entry = (pMCU_APP_ENTRY)*((uint32_t*)(MCU_CODE_IVT_ADDR + 4));
       app_entry();

       app_entry就是一个指向函数的指针,关于它的赋值操作,即第2条语句解释如下:

       MCU_CODE_IVT_ADDR,中断向量表起始地址,加4后的地址处存放的是Reset_handle的跳转地址(分析.s启动文件可知) ,因此这是区MCU_CODE_IVT_ADDR+4处的地址内存放的内容赋值给app_entry,即实现跳转到Reset_Handle程序,而Reset_Handle程序中就有对_main函数的跳转,即跳转到我们的APP程序中的main函数中,从而可以执行APP程序。

     

猜你喜欢

转载自blog.csdn.net/yhl_sophia/article/details/82702344