stm32启动方式分为(根据boot0,boot1的配置选择):
1)主闪存存储器= 芯片内置的Flash。这也是正常工作模式
2)SRAM = 芯片内置的RAM 区,就是内存啦。
3)系统存储器= 芯片内部一块特定的区域,芯片出厂时在这个区域预置了一段Bootloader,就是通常说
的ISP 程序。这个区域的内容在芯片出厂后没有人能够修改或擦除,即它是一个ROM 区。
第3种方式,是运行厂商自己的引导程序,用来从串口烧写程序,一般没什么用,我们一般使用第1种方式启动stm32 。但是i.mx6ull就不一样了,i.mx的厂商程序是一般都要运行的,它的负责选择从sd卡、nandflash、EMMC等启动,甚至需要负责初始化DDR3芯片,比stm32的厂商引导程序强大的多
stm32一般要实现类似i.max厂商引导程序这样的功能,需要自己实现,一般的实现方式是讲片上ROM分成两部分,0x0800 0000开始烧写自己实现的bootloader(这个bootloader一般用来ota升级使用),0x0800 2000开始烧写真正的程序,具体实现方式如下:
注意:
从0x0800 0000开始64个字节存放的是bootloader的bin文件指定的中断向量表
从0x0800 2000开始64个字节存放的是真正的程序bin文件指定的中断向量表
这些中断向量表都是在汇编文件startup_stm32f071xb.s中指定的
stm32上电,硬件这时自动从0x0800 0000位置处(中断向量表)读取数据赋给栈指针SP(这里存放的是栈顶指针),然后自动从0x0800 0004位置处读取数据赋给PC(这里存放的是复位中断服务程序),完成复位,也就是刚上电和按复位按钮的效果是一致的。
这里的SP和PC初始化都是烧写的Bootloader程序里面指定的,在bootloader程序运行完毕之前需要我们手动讲SP和PC初始化为0x0800 2000处中断向量表指定的值,也就是真正程序指定的值。
我们来看一下一份bootloader示例代码:
#include
#include
#include "flash_map.h"
#include
#include "crc32.h"
#include "ota.h"
#include "crc16.h"
#include "adp_flash.h"
#include "bsp_clock.h"
#include "debug.h"
int main(void)
{
uint16_t tmp_crc16 = 0;
bool err_program = false;
stUpdataData_t ota_para;
clock_hsi_config();
HAL_MspInit();
//初始化外置flash
ext_flash_init();
//负责校验外置flash中的bin文件,并烧写到内置flash的0x0802 0000处
int ret = ota_begin();
if(ret == 0) //编程OK
{
ota_jpapp();
}
else //编程失败
{
NVIC_SystemReset();//重启
while(1);
}
}
#define APPLICATION_ADDRESS 0x08002000
typedef void (*pFunction)(void);
pFunction Jump_To_Application;
uint32_t JumpAddress;
void ota_jpapp(void)
{
JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);
Jump_To_Application = (pFunction) JumpAddress;
__set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
Jump_To_Application();
}
可以看到,bootloader完成ota功能之后,调用了ota_japp()函数,从0x0800 2000处读取栈顶指针付给MSP(也就是SP),从0x0800 2004处读取复位中断服务程序 函数指针付给PC。
这样真正的程序就开始运行了,但是运行之前还需要做一件事,现在生效的中断向量表仍然是bootloader的中断向量表(0x0800 0000处的),需要让真正程序(0x0800 2000处)的中断向量表生效。如果是cortex-m3内核的stm32,比较简单,直接修改中断向量表偏移寄存器SCB->VTOR 为0x2000,即可让0x0800 2000处的中断向量表生效,即调用一下函数(ST官方库函数):
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset){
assert_param(IS_NVIC_VECTTAB(NVIC_VectTab));
assert_param(IS_NVIC_OFFSET(Offset));
SCB->VTOR = NVIC_VectTab | (Offset & (uint32_t)0x1FFFFF80);
}
但是,cortex-m0内核没有SCB->VTOR这个寄存器,所以只支持中断向量表位于 ROM起始处(0x0800 000),或者ram起始处(0x2000 0000),无法设置偏移,而且ROM起始处已经被bootloader的中断向量表占用了,下一次芯片上电还要用,不能动,所以只能将0x0800 2000处的中断向量表 拷贝到 RAM起始处(0x2000 0000),并设置,中断向量表从RAM起始处生效。如下(这里0x0800 2000处跑的是rtthread系统,所以将拷贝中断向量表的任务放在main函数中,运行完会startup_stm32f071xb.s汇编文件就会运行此main函数):
int $Sub$$main(void)
{
//拷贝0x0800 0000处中断向量表到0x2000 0000,即RAM起始处
ota_iap_set();
rt_hw_interrupt_disable();
rtthread_startup();
return 0;
}
void ota_iap_set(void)
{
uint32_t i = 0;
for(i = 0; i < 64; i++)
{
*((uint32_t*)(0x20000000 + (i << 2))) = *(__IO uint32_t*)(APPLICATION_ADDRESS + (i<<2));
}
__HAL_RCC_SYSCFG_CLK_ENABLE();
__HAL_SYSCFG_REMAPMEMORY_SRAM();//设置中断向量表从RAM起始处生效,而不是ROM起始处
}
至此,stm32的启动方式就讲完了
I.mx6ull也是可以根据BOOT_MODEL[0:1]引脚选择启动方式:
00:Boot From Fuses(熔丝)一般不用
01:Serial Downloader(从串口或者usb下载程序用)
10:Internal Boot(厂商引导程序)
I.mx6ull一般选择10,从内部boot rom启动(和stm32还是有区别的,stm32一般不用厂商引导程序,而6ull一般都是通过厂商引导程序启动),厂商自己的引导程序可以直接把我们的功能代码拷贝到指定的位置,并且支持多种启动设备。我们只需要用正点原子提供的烧录工具 imxdownload 将bin文件烧录到sd卡即可,此工具会为bin文件加一个头部,厂商自己的引导程序根据这个头部信息初始化ddr3,并根据这个头部信息拷贝代码到指定的ddr3地址。