公司在开发一款智能眼镜,使用STM32L0系列芯片作为主控芯片,蓝牙连接,总体来说不是很复杂。在发给客户测试的时候发现了一些问题,需要重新更新程序。这在开发人员看来只要两三下的事情,在客户手里可能就是一个巨麻烦的事情。所以决定给设备添加在线升级功能,通过蓝牙将新的固件更新到主控芯片里,而bootloader就是OTA中不可或缺的一部分。

  bootloader其实就是一段启动程序,它在芯片启动的时候首先被执行,它可以用来做一些硬件的初始化,当初始化完成之后跳转到对应的应用程序中去。

  我们可以将内存分为两个区,一个是启动程序区(0x0800 0000 – 0x0800 2000 )大小为8K Bytes,剩下的为应用程序区(0x0800 2000 – 0x0801 0000)。

  芯片上电时先运行启动程序,然后跳转到应用程序区执行应用程序。

  bootloader一个主要的功能就是首先程序的跳转。在STM32中只要将要跳转的地址直接写入PC寄存器,就可以跳转到对应的地址中去。

怎么实现呢?

  当我们实现一个函数的时候,这个函数最终会占用一段内存,而它的函数名代表的就是这段内存的起始地址。当我们调用这个函数的时候,单片机会将这段

内存的首地址(函数名对应的地址)加载到PC寄存器中,从而跳转到这段代码来执行。那么我们也可以利用这个原理,定义一个函数指针,将这个指针指向我们

想要跳转的地址,然后调用这个函数,就可以实现程序的跳转了。

  代码如下:

  1. #define APP_ADDR 0x08002000 //应用程序首地址定义
  2. typedef void (*APP_FUNC)(); //函数指针类型定义
  3.  
  4. APP_FUNC jump2app; //定义一个函数指针
  5.  
  6. jump2app = ( APP_FUNC )(APP_ADDR + 4); //给函数指针赋值
  7. jump2app(); //调用函数指针,实现程序跳转

  上面的代码实现了我们要的跳转功能,但是为什么要跳转到(APP_ADDR + 4) 这个地址,而不是APP_ADDR 呢?

  首先我们要了解主控芯片的启动过程。以STM32为例,在芯片上电的时候,首先会从内存地址位0x0800 0000(由启动模式决定)的地方加载栈顶地址(4字节),从0x0800 0004的地方加载程序复位地址(4字节),然后跳转到对应的复位地址去执行。

  所以上面的程序会中,jump2app这个函数指针的地址为(APP_ADDR + 4),调用这个函数指针的时候,芯片内核会自动跳转到这个指针指向的内存地址,也即是应用程序的复位地址。

  实际运行会发现,上面的程序可能会出现问题。因为我们还缺少了一个栈地址的加载过程,也就是芯片上电的第一个动作。这里要用到一点汇编的知识:

  1. __asm void MSR_MSP(uint32_t addr)
  2. {
  3. MSR MSP, r0
  4. BX r14;
  5. }

 __asm void MSR_MSP(uint32_t addr) 是MDK嵌入式汇编形式。

 

  1. MSR MSP, r0 意思是将r0寄存器中的值加载到MSP(主栈寄存器,复位时默认使用)寄存器中,r0中保存的是参数值,即addr的值
  1. BX r14 跳转到连接寄存器保存的地址中,即退出函数,跳转到函数调用地址

      完整的程序如下:
  1. #define APP_ADDR 0x08002000 //应用程序首地址定义
    typedef void (*APP_FUNC)(); //函数指针类型定义

    /*
    *
  2. * @brief
  3. * @param
  4. * @retval
  5. */
  6. __asm void MSR_MSP(uint32_t addr)
  7. {
  8. MSR MSP, r0
  9. BX r14;
  10. }
  11. /**
  12. * @brief
  13. * @param
  14. * @retval
  15. */
  16. void run_app(uint32_t app_addr)
  17. {
  18. uint32_t reset_addr = 0;
  19. APP_FUNC jump2app;
  20. /* 跳转之前关闭相应的中断 */
  21. NVIC_DisableIRQ(SysTick_IRQn);
  22. NVIC_DisableIRQ(LPUART_IRQ);
  23. /* 栈顶地址是否合法(这里sram大小为8k) */
  24. if(((*(uint32_t *)app_addr)&0x2FFFE000) == 0x20000000)
  25. {
  26. /* 设置栈指针 */
  27. MSR_MSP(app_addr);
  28. /* 获取复位地址 */
  29. reset_addr = *(uint32_t *)(app_addr+4);
  30. jump2app = ( APP_FUNC )reset_addr;
  31. jump2app();
  32. }
  33. else
  34. {
  35. printf("APP Not Found!\n");
  36. }
  37. }
  1.  

  我们需要在设置界面将默认(0x8000000)改为我们的应用程序地址(0x8002000

  

 

  完成了上面的工作,实际测试发现程序还是无法正确运行。原因是我们没有进行中断向量表的重映射。向量表映射?什么时候有做过这个工作,我们来看一下:

.s文件里有如下代码:

 

  1. ; Reset handler routine
  2. Reset_Handler PROC
  3. EXPORT Reset_Handler [WEAK]
  4. IMPORT __main
  5. IMPORT SystemInit
  6. LDR R0, =SystemInit
  7. BLX R0
  8. LDR R0, =__main
  9. BX R0
  10. ENDP

 

 

这段代码表示,程序在执行main函数之前,会先执行SystemInit这个函数。下面看看这个函数到底做了什么东西:

 

  1. /**
  2. * @brief Setup the microcontroller system.
  3. * @param None
  4. * @retval None
  5. */
  6. void SystemInit (void)
  7. {
  8. /*!< Set MSION bit */
  9. RCC->CR |= (uint32_t)0x00000100U;
  10. /*!< Reset SW[1:0], HPRE[3:0], PPRE1[2:0], PPRE2[2:0], MCOSEL[2:0] and MCOPRE[2:0] bits */
  11. RCC->CFGR &= (uint32_t) 0x88FF400CU;
  12. /*!< Reset HSION, HSIDIVEN, HSEON, CSSON and PLLON bits */
  13. RCC->CR &= (uint32_t)0xFEF6FFF6U;
  14. /*!< Reset HSI48ON bit */
  15. RCC->CRRCR &= (uint32_t)0xFFFFFFFEU;
  16. /*!< Reset HSEBYP bit */
  17. RCC->CR &= (uint32_t)0xFFFBFFFFU;
  18. /*!< Reset PLLSRC, PLLMUL[3:0] and PLLDIV[1:0] bits */
  19. RCC->CFGR &= (uint32_t)0xFF02FFFFU;
  20. /*!< Disable all interrupts */
  21. RCC->CIER = 0x00000000U;
  22. /* Configure the Vector Table location add offset address ------------------*/
  23. #ifdef VECT_TAB_SRAM
  24. SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
  25. #else
  26. SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
  27. #endif
  28. }

 

  从上面的代码可以看到,这个函数主要是做了时钟的初始化和中断初始化,还有就是中断向量表的映射,就是最后那一段代码

  

  再看看FLASH_BASE 和 VECT_TAB_OFFSET的定义:

  

  

   哈哈,这里默认映射的地址就是FLASH的初始地址,所以只要将其改成我们程序的起始地址就行了: SCB->VTOR = 0x08002000

  编译,运行,下载,完美运行!

  程序跳转完成,对于bootloader来说也就完成了一大半。剩下的就是根据自己的需求去完善相应功能了,比如我的在线升级功能,就要在bootloader里做固件接收和校验的功能。这里有一点需要特别注意的是,跳转程序之前最好把你用到的中断都关了,不然跳转之后的程序没有对应的中断处理函数,那就又可能使得程序进入死循环中。

 

  1.  

 

版权声明:本文为jiuliblog-2016原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/jiuliblog-2016/p/11411887.html