一文带你了解并实现嵌入式OTA

1. 前言

  我们前面已经完整的实现了所有的功能,而如果我们的客户在使用过程中,想加入新功能了,此时怎么办呢?----你可能会说那重新烧录一份不就好了?可是在现实的生产环境中,可能往往不会给你留物理接口供你烧录,你也许可能通过网络等下发文件,此时就用到我们今天的程序了!

2. 什么是bootloader?

  bootloader也是一个程序,只不过这个这个程序比较特殊,他的作用是:

  1. 接收升级文件
  2. 跳转到指定区域执行APP。
      1很好理解,但是2是什么呢?怎么跳转呢?跳转了之后怎么就知道怎么执行APP了,那这件事就说来话长了,且听我慢慢道来

2.1 STM32启动过程都做了什么?

  相信你一定有个疑问?—为啥程序知道我们要从main函数中开始呢?让我们从STM32上电复位那一刻起,看看都发生了什么。
  当我们上电或者复位后,我们的PC指针首先会根据BOOT0和BOOT1的设置,决定系统从哪里启动。
启动过程
  以我们最熟悉的从flash中(0x8000 0000)启动为例,此时到了这个位置之后,先做那几件事呢?–有这么两件事是Cortex-M3规定好的

  1. 从0x8000 0000中得到SP栈顶的值,此时有了栈才有了运行环境嘛
  2. 从0x8000 0004中取出值给PC指针,此时PC指针有了值时候,就可以一条条指令运行了
    那么问题来了,0x8000 0004中的值到底是多少,怎么设置呢?
    这时候就需要查看我们的startup_stm32xxx.s启动文件了!
    启动文件
    我们来看这程序中,超级重要的一块内容!
; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                EXPORT  __Vectors
                EXPORT  __Vectors_End
                EXPORT  __Vectors_Size

__Vectors       DCD     __initial_sp               ; Top of Stack
                DCD     Reset_Handler              ; Reset Handler
                DCD     NMI_Handler                ; NMI Handler
                DCD     HardFault_Handler          ; Hard Fault Handler
                DCD     MemManage_Handler          ; MPU Fault Handler
                DCD     BusFault_Handler           ; Bus Fault Handler
                DCD     UsageFault_Handler         ; Usage Fault Handler
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     SVC_Handler                ; SVCall Handler
                DCD     DebugMon_Handler           ; Debug Monitor Handler
                DCD     0                          ; Reserved
                DCD     PendSV_Handler             ; PendSV Handler
                DCD     SysTick_Handler            ; SysTick Handler
                ; External Interrupts
                DCD     WWDG_IRQHandler                   ; Window WatchDog                                        
                DCD     PVD_IRQHandler                    ; PVD through EXTI Line         

在这里插入图片描述

  意思就是我把这些东西会放在整个bin文件的最开始,那第一个果然就是给SP指针—栈顶的值,那看起来PC指针此时就指向了ResetHadndler,接着来看ResetHandler做了什么

; Reset handler
Reset_Handler    PROC
                 EXPORT  Reset_Handler             [WEAK]
        IMPORT  SystemInit
        IMPORT  __main
                 LDR     R0, =0xE000ED88    ; 使能浮点运算 CP10,CP11
                 LDR     R1,[R0]
                 ORR     R1,R1,#(0xF << 20)
                 STR     R1,[R0]
                 LDR     R0, =SystemInit
                 BLX     R0
                 LDR     R0, =__main
                 BX      R0
                 ENDP

; Dummy Exception Handlers (infinite loops which can be modified)

  看起来在 Reset_Handler 中,执行了SystemInit 和 __main两个函数。这里简单介绍一下这俩函数做什么?

  1. SystemInit:配置时钟
  2. __main:配置运行需要的环境,最终跳转到main函数
      此时费劲千辛万苦,终于跳转到我们的main函数执行了!
    所以我们总结一下启动过程
  • 根据BOOT0和BOOT1,决定从哪里驱动
  • 依次初始化栈顶指针SP 和 PC指针指向ResetHandler
  • 执行ResetHadnler,依次执行SystemInit()和__main
  • 跳转main函数开始执行

2.2 中断发生的时候都做了什么?

  这里就不多讲了,还是想说前面提到的startup.s内个段(中断向量表)的重要性,因为他保证了中断发生之后,根据中断的类型,PC指针能快速找到对应中断服务程序的位置,然后跳转去执行。
  那中断发生后,CPU是怎么知道这个中断向量表( AREA RESET, DATA, READONLY)的起始位置在哪呢,就是有一个vector寄存器保存了中断向量表的起始位置

 SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET; 

3. 实现我们的bootloader

  我们在知道了启动流程和中断处理的时候的流程,我们就知道我们想要实现跳转APP的时候,需要做哪些工作了。

  1. 首先找到APP的bin文件在Flash中的起始位置
  2. 然后根据起始位置的值,初始化APP的SP指针
  3. 然后bootloader直接访问 起始地址 + 4,也就是APP的ResetHandler
  4. 这个ResetHandler执行完之后,就到了APP的main函数
  5. 在APP main函数第一件事:修改vector寄存器的值,指向APP对应的中断向量表起始地址(不修改的话还是bootloader的中断向量表的起始地址)(5的修改也可以放在4的前面)

3.1 bootloader 引导下的APP的注意事项

  1. 一定记得修改vector寄存器的值!
      试想一下,如果不改的话,在APP中发生中断了,就会跑去bootloader的中断向量表去查中断服务程序了,此时的行为就不可预测了。
  2. 跳转过程要关中断
      因为bootloader程序和APP程序是共享中断的使用了,在跳转过程中最好屏蔽所有的中断,然后在APP中再打开中断。
      系统时钟sysTick也禁止
  3. 跳转前最好清理掉所有的中断寄存器
      bootloader根据需要可能会使用中断,试想一下跳转过程前恰好触发了bootloader某个中断,此时跳转到APP,APP一开中断,可能会有意想不到的效果
  4. 关于MSP和PSP
      这就涉及到RTOS和裸机环境了,在裸机环境中我们一般任何时候使用的MSP指针,而在RTOS时,使用的是PSP指针。但是奥,大伙注意,我们就算APP用的RTOS的环境,启动RTOS之前,还是裸机环境,用的MSP指针
      在这里不想分类讨论了,在跳转之前就做这两件事就好:
    1. 设置PSP 为 0 :__set_PSP(0);
    2. 使用MSP指针:__set_CONTROL(0);
  5. 回顾
      跳转之所以麻烦,问题的根源在哪呢?在CPU的寄存器对于APP和bootloader是共享的。试想只要由于bootloader修改了某个寄存器,导致寄存器状态和APP期望的初始状态不一致,就有可能出问题。

3.2 代码实现(仅跳转部分)


__set_PRIMASK(1);//bootloader在进入app之前使用__set_PRIMASK(1);函数关闭中断,在app中需要将中断打开__set_PRIMASK(0);
SysTick->CTRL = 0X00;//禁止SysTick
SysTick->LOAD = 0;
SysTick->VAL = 0;
//... Disable用到的中断....
//... 清空所有这些中断的位...
 NVIC_DisableIRQ(USART1_IRQn);
 NVIC_DisableIRQ(EXTI2_IRQn);     
 NVIC_ClearPendingIRQ(USART1_IRQn);
 NVIC_ClearPendingIRQ(EXTI2_IRQn);

 __set_PSP(0);		// 清空PSP
 __set_CONTROL(0);//使用操作系统后系统内核会使用PSP模式,跳转到APP后没有恢复到MSP模式就会导致内存异常从而进入到内存异常中断
 jump2app = (iapfun) * (volatile uint32_t *)(appxaddr + 4);	 //APP的ResetHandler
/* 初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址) */
sys_msr_msp(*(volatile uint32_t *)appxaddr);				// APP的SP指针
jump2app();

在APP中要做的

sys_nvic_set_vector_table(FLASH_BASE, 0x80000); // 设置中断向量表的偏移量
__set_PRIMASK(0);//解除中断屏蔽,打开中断

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值