ARM单片机OTA解析(一)
文章目录
- 一、单片机烧写程序的几种方法
- 二、Bootloader如何加载启动App
一、单片机烧写程序的几种方法
在线应用编程,由开发者实现Bootloader功能,比如ARM单片机的Code分区中的Flash本是存储用户应用程序的区间(上电从此处执行用户代码),开发者可以将自己实现的Bootloader和应用程序都存放到Flash区间MCU上电启动先执行用户的Bootloader代码,该代码可为用户应用程序的下载、校验、升级、启动等提供支持,进而实现OTA远程升级功能。
一般用于给用户远程升级,或者是烧写程序不方便的时候。
复位以后进入Bootloader程序,
需要说明的是这个地方就是我们程序烧写的其实地址,
这是因为我们要把前面的空间流出来给BOOT程序。
ARM单片机启动流程(一)(详细解析)-CSDN博客 在本人的这篇文章里面可以看到我们的Main Flash的物理其实地址就是0x08000000
,因此我需要把BOOT程序烧写到这里,因为CPU执行程序最先
CPU都是从0地址开始访问的,根据被引导到的地方,有可能直接跳转到Main Flash 0x0800 0000的原始存储空间;也有可能跳转到MCU厂商预置的bootloader开始于0x1FFF F000的原始存储空间。至于怎么跳转这两个以及默认是什么,连接文章在这一块进行了详细分析。
一般我们默认就是下面顺序:我们就是直接跳转到Main Flash 0x0800 0000的原始存储空间;
(CPU通过程序计数器(PC)获取下一条指令的地址。在无分支的情况下,PC自动增加(如 PC_new = PC_old + 指令长度
),实现物理地址的顺序执行)
int main(void)
{ InitIrqAfterBoot();DrvInit();AppInit();while (1){TaskHandler();}
}void InitIrqAfterBoot(void)
{nvic_vector_table_set(NVIC_VECTTAB_FLASH, 0x3000);__enable_irq();
}#define NVIC_VECTTAB_FLASH ((uint32_t)0x08000000) /*!< Flash base address */
值得注意的是在APP程序里面,需要配置中断向量表里面的初始地址,这是因为原本我们默认的程序初始地址是0x08000000
,但是由于BOOT程序的作用导致我们的初始地址变成了((uint32_t)0x08000000)+偏移地址0x3000
,中断向量表的初始位置。
如果我们常规的程序,是CPU直接加载的,就不需要我们初始化,就是内核会默认帮我们搞好,从0x08000000
开始。
但是我们现在的程序是从BOOT里面启动的,那就意味着我们需要自己配置一个中断向量表初始地址,所以我们需要在应用程序里面做一些CPU需要做的事情,并且在BOOT程序里面一样需要做一些CPU需要做的事情,说白了就是手动干一些CPU的事情。
同样需要烂熟于心的还有0x3000
表示12KB,0x1000
表示4KB,也就是4096个字节(Byte)。
另外需要一点注意的:
.property = R | W,.address = 0x0004, // 触发系统复位 01 06 00 04 00 01.minValue = 0,.maxValue = 1,.WriteCb = ModbusResetSystem,static void ModbusResetSystem(uint16_t value)
{ResetToBoot();
}void ResetToBoot(void)
{__disable_irq(); //关闭所有中断NVIC_SystemReset(); //复位函数,需要一些执行的时间
}
在APP程序里面,触发复位的时候,我们一定要先关闭所有中断,如果不关闭中断可能会导致我们的BOOT程序会出现错误,因为BOOT里面也需要中断。
此外需要联系之前的知识在做一次对比:
代码段1:
__Vectors0x08000000: 20000428 (.. DCD 5368719760x08000004: 08000145 E... DCD 1342180530x08000008: 0800014d M... DCD 1342180610x0800000c: 0800014f O... DCD 1342180630x08000010: 08000151 Q... DCD 1342180650x08000014: 08000153 S... DCD 1342180670x08000018: 08000155 U... DCD 1342180690x0800001c: 00000000 .... DCD 00x08000020: 00000000 .... DCD 00x08000024: 00000000 .... DCD 00x08000028: 00000000 .... DCD 00x0800002c: 08000157 W... DCD 1342180710x08000030: 08000159 Y... DCD 1342180730x08000034: 00000000 .... DCD 00x08000038: 0800015b [... DCD 1342180750x0800003c: 0800015d ]... DCD 1342180770x08000040: 0800015f _... DCD 1342180790x08000044: 0800015f _... DCD 1342180790x08000048: 0800015f _... DCD 1342180790x0800004c: 0800015f _... DCD 1342180790x08000050: 0800015f _... DCD 1342180790x08000054: 0800015f _... DCD 1342180790x08000058: 0800015f _... DCD 134218079
代码段2:
__Vectors0x08003000: 20000738 8.. DCD 5368727600x08003004: 08003145 E1.. DCD 1342303410x08003008: 08003d21 !=.. DCD 1342333770x0800300c: 08003965 e9.. DCD 1342324210x08003010: 08003c69 i<.. DCD 1342331930x08003014: 0800356b k5.. DCD 1342314030x08003018: 08004531 1E.. DCD 1342354410x0800301c: 00000000 .... DCD 00x08003020: 00000000 .... DCD 00x08003024: 00000000 .... DCD 00x08003028: 00000000 .... DCD 00x0800302c: 0800402d -@.. DCD 1342341570x08003030: 0800365b [6.. DCD 1342316430x08003040: 0800315f _1.. DCD 1342303670x08003044: 0800315f _1.. DCD 1342303670x08003048: 0800315f _1.. DCD 1342303670x0800304c: 0800315f _1.. DCD 1342303670x08003050: 0800315f _1.. DCD 1342303670x08003054: 0800315f _1.. DCD 134230367
可以明显看出初始的中断向量地址就是0x08003000:
开始的,跟之前的0x08000000:
有明显的地址偏移量,而这个地址偏移量就是我们自己设计的0x3000
。
二、Bootloader如何加载启动App
首先我们看一下ROM空间的分布,
相当于前面12KB划分给了BOOT,后面的500KB空间给了APP。并且本例程使用的GD32这个单片机一共的FLASH空间就是512KB。
首先是读取 0x08000000:
获取栈顶地址,
接着是读取 0x08000004:
获取复位函数的地址
然后跳转到复位函数地址 0x08000004:
执行复位函数的代码指令,这些是CPU自动完成的。
这里需要说明的为什么CPU需要执行复位函数?
建立可预测的初始状态,消除不确定性,确保系统行为可预测。
CPU上电或复位时,寄存器、程序计数器(PC)、状态标志等内部状态是随机的或残留前次运行的错误值。复位函数会强制将其清零或设为预设值(如PC指向复位向量地址0xFFFF0
),使CPU从已知起点开始执行。
程序计数器(PC)清零:复位后PC指向固定的启动地址(如ARM的0x00000000
或x86的0xFFFF0
),加载第一条指令
寄存器初始化:通用寄存器、状态寄存器(如EFLAGS)恢复默认值,避免残留数据干扰新程序。
此外当系统遇到致命错误时,复位是恢复运行的终极手段:
- 软件错误:如堆栈溢出、死循环、空指针访问等,通过看门狗定时器触发复位。看门狗超时未清零则强制复位,脱离卡死状态。
- 硬件故障:内存错误、总线冲突、电源欠压等触发复位以保护硬件。例如,STM32的欠压检测(VBOR)会直接复位CPU。
- 中断与死锁:多核系统中核心间死锁可通过内核复位(如ARM的
VECTRESET
)局部恢复,避免全系统重启。
那如何启动APP?
我们使用BOOT启动APP的时候也是需要干这些动作,只不过这些动作不是CPU帮助我们自动完成了,而是需要我们自己手动完成。
代码详细解析:
static void BootToApp(void)
{uint32_t stackTopAddr = *(volatile uint32_t*)APP_ADDR_IN_FLASH; if (stackTopAddr > RAM_START_ADDRESS && stackTopAddr < (RAM_START_ADDRESS + RAM_SIZE)) //判断栈顶地址是否在合法范围内{__disable_irq();__set_MSP(stackTopAddr);uint32_t resetHandlerAddr = *(volatile uint32_t*) (APP_ADDR_IN_FLASH + 4);/* Jump to user application */pFunction Jump_To_Application = (pFunction) resetHandlerAddr; // int *p = (int *)0x8003145/* Initialize user application's Stack Pointer */Jump_To_Application();}NVIC_SystemReset();
}
文章源码获取方式:
如果您对本文的源码感兴趣,欢迎在评论区留下您的邮箱地址。我会在空闲时间整理相关代码,并通过邮件发送给您。由于个人时间有限,发送可能会有一定延迟,请您耐心等待。同时,建议您在评论时注明具体的需求或问题,以便我更好地为您提供针对性的帮助。
【版权声明】
本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议。这意味着您可以自由地共享(复制、分发)和改编(修改、转换)本文内容,但必须遵守以下条件:
署名:您必须注明原作者(即本文博主)的姓名,并提供指向原文的链接。
相同方式共享:如果您基于本文创作了新的内容,必须使用相同的 CC 4.0 BY-SA 协议进行发布。
感谢您的理解与支持!如果您有任何疑问或需要进一步协助,请随时在评论区留言。