boot和app其实没什么区别,只是位置不同的2段代码,只不过先运行boot然后才运行app。

        拿一个点灯工程的例子来说,我们编译烧录到单片机里去,运行,灯被点亮了。其实这一段代码我们可以把它叫做app。

        那么我想在点灯之前干一些其他事情,比如说开一个定时器或者串口,那我们就可以在app中初始化定时器或者串口,然后编译烧录运行,就可以用定时器或者串口了,每次想增加什么功能,我就修改源代码然后编译烧录进入,覆盖掉我之前的旧代码。

        那客户那边需要我升级我的点灯程序,把它变成呼吸灯,但是它不想把我以前的点灯功能丢掉,,客户想要它先点灯然后在变成呼吸灯,我想这个简单啊,就在我点灯代码的后面加一个呼吸道不就行了吗,好改完重新烧录,实现功能了。

        但是客户想要高级的升级方式,像蓝牙、串口、无线升级等方法,那我不能在像以前一样用连接后在烧录的方式了,我需要的是把升级的部分发送给单片机,然后让它自己给自己升级。

前提概念

1. 我们要理解stm32(以f103c8t6为例子)的boot启动流程,就必须要知道一些基本知识点

        - 芯片上电的第一条代码不是从main函数开始的

阅读Cortex权威指南里的内容可以找到

        - boot0boot1的作用

        通俗易懂的话来说,他们的电平组合决定stm32上电后的第一条代码从哪里开始执行,一般来说把boot0引脚接地也就是低电平,从Flash(0x08000000)中读取第一条代码。

        - 中断向量表(是boot或者app的一部分)

        现在我们默认代码存放的起始地址为0x08000000,那么中断向量表就存放在这个位置,顺序依次是MSP(堆栈指针值)ResetHandler....,(更多细节可以阅读Cortex3权威指南)中断向量值,我们就只看前2个MSP和ResetHandler中断向量,

        上电后读取的第一条代码就是获取MSP堆栈指针的值,然后进入读取ResetHandler地址中,ResetHandler在启动文件startup_stm32f100xb.s中我们可以找到这个函数干了什么

; Reset handler
Reset_Handler    PROC
                 EXPORT  Reset_Handler             [WEAK]
     IMPORT  __main
     IMPORT  SystemInit
                 LDR     R0, =SystemInit      // 先跳转到SystemInit这个函数运行
                 BLX     R0
                 LDR     R0, =__main          //之后才运行main函数
                 BX      R0
                 ENDP

        上面内容是ResetHandler中的内容,它会先跳转到SystemInit这个函数中去运行,然后才运行main函数,现在我们就知道了单片机的第一条代码不是从main函数开始的,而是先读取堆栈指针值,然后进入复位函数中。

        SystemInit里面我们就只需要知道,它使用了STM32的VTOR寄存器进行中断向量表重定向,什么意思呢,就是原本中断向量表表的位置是从0x08000000开始,但是我给VTOR寄存器一个值0x08002000后,原本中断向量表的位置就从0x08002000开始,这是把boot和app划分为2个部分的关键,有boot和没boot的关键区别就在与有没有重定向。下面我会详细说明

2. 当我们有一些基础知识后,我们在来看。

boot和app本质上是一样的,只不过存在与不同的地址区域

        其实我一开始也不理解为什么要有boot,直接在app中加入状态机将代码分为几个部分不就行了吗,为什么这么麻烦还要划分为2个区域什么的,其实分成2个部分更好管理和阅读,如果使用状态机或标志位的话,所有代码都放在一个工程里,工程小的话还好,一但工程量大了维护起来就十分的麻烦。

        那么我现在知道了,我需要2部分代码,第一部分代码是用来升级单片机自己,而另一部分就是被升级的部分,也就是呼吸灯部分。

        我现在把第一部分用来升级的部分称为boot,实现呼吸的部分称为app,那现在就有2个不同的代码部分了,我们看一下没划分的boot和划分boot后的内存情况

                                                                无boot内存分布

那么我现在要把他分为boot和app,

在这里其实就已经初步划分boot和app了,但是仔细思考就会知道,现在的boot和app其实和和在boot_main()函数中调用app_main()函数发生跳转是一样的,那不还是一样没有划分吗?

是的现在的boot和app其实还没分开,当有中断请求发生

发生中断以后,boot和app共用一个中断向量表,那么这样其实boot和app就只是2个不同的函数而已,并不是2部分划分代码,而且当app的中断出现问题时会影响到boot的中断,这是我们不想看到的。

        那我们现在知道了,要区分boot和app不仅仅要区分函数和地址,还要区分他们的中断向量表,也就是说我的boot有自己的中断向量表,app也有自己的中断向量表,也就是

这部分我们就真正区分了boot和app,可是问题又出现了,中断向量表不是像if else那样写出来就行了,默认中断向量表都是存储在程序开头的,我们如何把中断向量表搬到其他地方呢?

        到这里我们就需要看到一个Cortex内核中的寄存器VTOR。作用是中断向量表重定向,

现在我们有了VTOR寄存器,那也就是说明我可以把向量表放到我想要的位置,

只需要确保放向量表的地址中实际保存着向量表数据而不是空内存就可以了。

好,现在我通过boot程序中修改VTOR寄存器的值就可以进行boot和app的分离了

现在让我我们整理一下,

        -没有boot单片机启动流程为(默认boot0为低电平,也就是Flash为起始地址)

                1. 上电复位,到0x08000000读取设置msp堆栈指针值,

                2. 进入ResetHandler

                3. 跳转到(boot)main()函数中去

        -有boot单片机启动流程为

                1. 上电复位,到0x08000000读取设置msp堆栈指针值,

                2. 进入ResetHandler

                3. 跳转到(boot)main函数中去

                4. 运行一段时间后,准备跳转到app

                5. 关中断,修改VTOR到app程序的首地址+4,跳转

                6. 进入app中断向量表的ResetHandler

                7. 进入(app)main()

                8.开中断

这样我们就实现了boot和app的分离,而且举的例子比较简单,往后走可以实现单boot和多app共存的模式。

其实还有很多地方讲不明白,但那不是本篇的主体

        比如说:

                        boot的main()和app的main()其实函数名字是一样的那为什么没有冲突呢

                        这涉及到编译链接的内容,篇幅限制不好展开。

内容就是这些了,觉得有用或者有问题的话希望大家评论,我好即使修正,自己也只是个小菜鸡~

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐