前置知识解析:
首先,我们需要知道单片机的程序是如何运行的,先不说底层原理,我们单从实际现象上可以看得出来,单片机按下复位按键后,就开始执行main程序了
实际上,单片机的程序不是简简单单的从main开始执行的,实际情况需要我们阅读两个资料:cortex m3/4权威指南,以及一个简单的启动文件
先看cortex m3/4权威指南:

我们首先要知道一个事实:单片机上电后是从0x0000 0000 处开始运行的,权威指南也说得很清楚了:

从向量表也可以看得出来,地址0x0000 0000 处是MSP的初始值,将这个值赋给MSP,这是第一步
第二步,单片机通过内核的硬件机制,自动将PC指向复位中断向量,紧接着就是指向复位中断,这点在权威指南里面也说得很清楚:

对于第二步,可以通过启动文件进一步分析:

Vector的中文意思就是向量,图片中的就是中断向量表
中断向量表有什么用?是干什么的?再次回到权威指南寻找答案如下:

说白了就是存储不同中断的入口地址,比如外部中断啊,定时器中断啊,DMA中断啊......就是存储这些中断函数的入口地址
同时我们也可以得出,向量表是默认存储在物理地址0处的,刚刚好对应上面的地址0x0000 0000 处是MSP的初始值这句话
同时,权威指南里面的向量表结构也与启动文件的向量表的结构一模一样
回到刚才说的PC指向复位中断向量,我们可以在启动文件找到复位中断函数:

我们看到,在复位函数里面,首先要进行SystemInit,通过跳转该函数,可以得出,SystemInit做的工作如下:
复位RCC时钟,同时配置HSI

中断向量表重定向:

其中:


紧接着,执行__main,这跟我们主程序的main是两个完全不同的函数,根据MDK文档:
It is automatically created by the linker when it sees a definition of main()。简单点来说,当编译器发现定义了main函数,那么就会自动创建__main,这个函数在IDE中找不到,得在反汇编文件.map里面找,执行完后,才会跳转到main主函数执行主程序
之前如果你完全没有接触甚至听说过OTA,只是说在keil里面写好程序,烧录就行,那么,你的代码执行流程就是这样
额外扩展:在系统存储器里面启动程序
一般情况下,我们的程序都是从flash开始运行的,flash的起始地址是0x8000 0000,但是之前说的起始地址是0x0000 0000,产生了地址不一致的问题,这就引出了存储器映射:
STM32 通过存储器映射机制,将 FLASH 的内容映射到地址 0x0 处。当 CPU 访问 0x0 地址时,实际上访问的是 0x08000000 处的 FLASH 内容。这种映射是硬件自动完成的,对软件透明,当然这是当BOOT0与BOOT1都处于GND的情况下
通过STM32F10参考手册,如下:

这三种启动方式对应的物理地址如下:

同时参考手册有这么一句话需要特别注意:

先看第二种:系统存储器启动:
系统存储器里面是ST官方写的一个名为BootLoader的程序,也叫自举程序,如果说用户想要使用串口烧录程序,就需要实验这个模式,这种方法可以通过ST官方的BOOTLoader的程序,将从串口下载的程序内置到flash中
也就是,之前通过ST-LINK啊,DAP LINK啊, JLINK啊,是直接将程序下载到flash里面的,这次是通过BootLoader将程序放到flash的,也就是偶尔听过的串口下载程序,两者相同之处都是在flash启动程序
通过这里,其实已经可以引出OTA了,既然bootloader有这种功能,那么我们是不是也可以自己写一个bootloader?获取串口接收的代码文件,将其放到指定区域,而且我们也不一定要用串口,是不是也可以通过无线模块,只要单片机可以无线获取bin程序文件,再通过我们以及向单片机内部写入的bootloader来加载程序文件,岂不是就是类似于无线烧录的方法了?比如使用HC-06蓝牙模块,或者ESP8266模块
将接收到应用程序全部正确写入Flash的App指定位置后,Bootloader 需要完成跳转到应用程序App的开始位置的操作,这也是Bootloader 的核心作用
通过执行下面代码,即可实现跳转至APP程序:
- 首先需要关闭所有中断,因为跳转过程中不能被中断打断
- 规定新的APP程序的起始地址为ApplicationAddress


- 接着通过位置偏移+4字节,定位到复位向量地址,因为ApplicationAddress定义为新程序的起始地址,由之前可得,为MSP初始值

将复位中断向量作为APP的入口函数,语法上就是将APP的复位中断向量的地址作为函数指针,

紧接着需要设置APP程序的MSP的起始地址

接下来只需要通过Jump_To_Application();即可进行调用
紧接着,需要进行中断向量表偏移,因为原来的中断向量表是bootloader程序,新的APP程序存储在0x0800 5000位置,相对偏移了0x5000,中断向量表也需要对应进行偏移0x5000,否则在新的APP程序发生中断的时候,无法找到对应的中断向量表,从而造成中断混乱,
在之前的分析SystemInit的时候,这个语句其实就是实现的VTOR的地址设置

其中VECT_TAB_OFFSET设置的就是中断向量表的偏移量,设置的时候需要注意注释中的内容,数值要为0x100的倍数(应该是考虑到地址对齐的因素,不同的芯片,这里的注释也不愿意,有的是0x200倍数),可见其默认值是0,再结合上面的语句,也可以得出一个结论:中断向量表默认从flash的起始地址开始存储

总结为一张图片如下:
没有引入BootLoader之前,系统程序的启动流程:

加入BootLoader后,系统程序的启动流程:

编写BootLoader:
STM32:
可以参考官方的例程:STSW-STM32008 | Product - 意法半导体STMicroelectronics
需要注意,官方的代码有缺陷,就是没有进行中断向量表的偏移,如果测试的APP带有中断,将会导致运行错误
GD32:
我近段时间仿造STM32的官方代码,以及GD32的库函数,以及CSDN上其他大佬的代码,我自己针对GD32F407VET6写了一份,带有BootLoader与测试APP,欢迎来我仓库下载:
具体是怎么修改的以及注意事项我都写在了BootLoader程序的注释里面,看注释就行,不懂的评论区说出来我有空看看
GD32_OTA: GD32F407VET6实现OTA升级例程代码
测试的时候,需要注意根据实际的APP地址,对两个程序的FLASH的start与size都进行修改:
