本篇文章将介绍flash算法文件,阐述从jlink如何下载镜像文件写入到内部的falsh。
在谈flash下载算法文件时,先说明XIP是什么。
芯片的启动方式有很多种:可以从RAM中启动、内部的flash、外部的flash等等(还有从sd卡、emmc、nor flash、nand flash等),这里我们只考虑内部的flash的情况。
我们都知道flash只是一块ROM,flash有两种类型,分为nor flash、nand flash,一般32位处理器里面使用的是nor flash作为ROM,存放镜像文件。
在专业课(计算机组成原理或者操作系统)中,有提到,CPU从内存(RAM)中读取、运行程序。但在stm32这种微处理器上,一般都是直接从内部的flash启动。这牵扯到一个技术,叫做XIP(eXecute In Place ),即芯片内执行。其含义就是CPU直接从存储器读取指令,将指令送给译码器和执行器等部件使用。 有了XIP就不必将代码读取到RAM中,可以直接在flash运行。好处即是程序代码无需占用内存,减少内存的要求。XIP是复杂性和速度的权衡,而这就意味着XIP通常仅用于BIOS或RAM极度短缺(MCU的情况就是如此)的情况。
要实现XIP,必须要有如下几个条件:
1. 存储器必须提供与内存相似的接口给CPU。
2. 该接口必须提供足够快的读取操作,并具有随机访问模式。
3. 如有文件系统,则需要提供合适的映射功能
4. 程序链接时需要知道存储器的地址或地址与位置无关。
5. 程序不能修改已加载映像中的数据。
因为nor flash和EEPROM通常能满足上述要求,所以其可以XIP。 而nand flash因为有地址,数据,命令共用IO口的问题,cpu发送来地址之后,还要nand flahs控制器再处理一下才能得到数据,因此不适合(不是不能XIP,只是不适合)。nor flash的访问和RAM类似,提供地址,就可以得到数据。因此,一般处理器内部都是使用nor flash作为XIP的方案,而且,nor flash读取速度比nand flash要快很多。
当bin文件经过JLink、SW-DP接口,经过AHB-DP将数据传到AHB总线上之后,已经具备可以写入到falsh里面的条件了。这里随便找了一个芯片内部图作为例子。
从上图中可以看到,FLASH上面有Controller接口、XIP接口、EEPROM仿真接口。Controller接口就是我们在平常使用程序往flash写入数据的控制部分(一般流程是:先解锁flash写保护、写入数据、上锁写保护)。这个例子应该很容易想起来的,不懂的可以搜索一下:stm32往内部flash写入数据。就可以得到相关信息了。这里我们写入bin文件到flash同样通过Controller接口。
SWD接口将数据传到AHB-DP、到总线之后,就会在控制信号的指引下,控制Controller,然后将bin文件写入到flash,写入完成、校验之后,就可以reset启动芯片了,使用XIP技术,从flash某个地址启动(常用的stm32一般是0x08000000)。
知道了往哪里写之后,也大概了解了数据写入的流程,那么该如何写?这里就要用到了flash下载算法了。
FLM文件是keil进行代码下载时的必须文件,该文件主要包含被下载芯片的存储器的相关信息(芯片型号、存储器页大小等)和存储器写入算法。FLM文件本质上也是一种ELF(Executable and Linkable Format)文件,ELF文件的具体格式我们放到后面介绍。
上图中就是FLM文件,他指导jlink如何下载镜像文件。镜像文件本质上就是一堆二进制,我们都知道keil会生成axf文件、hex、bin文件,镜像文件其实就是bin文件,下载到flash里面的内容就是bin文件的内容。算法的名字叫做NEW_DEVICE.FLM,路径在keil的安装目录里面:ARM\PACK\ARM\CMSIS\4.5.0\Device\ARM\Flash。
在C:\Keil_v5\ARM\Flash_Template(默认安装的情况下)下面会有一个keil工程,这个工程就是keil官方提供给我们制作flash下载算法的。
打开之后很简单,就两个文件
FlashDev.c就只有一个struct FlashDevice类型的结构体,上图是一个例子,具体定义在FlashOS.H中。
struct FlashSectors {
unsigned long szSector; // Sector Size in Bytes
unsigned long AddrSector; // Address of Sector
};
struct FlashDevice {
unsigned short Vers; // Version Number and Architecture
char DevName[128]; // Device Name and Description
unsigned short DevType; // Device Type: ONCHIP, EXT8BIT, EXT16BIT, ...
unsigned long DevAdr; // Default Device Start Address
unsigned long szDev; // Total Size of Device
unsigned long szPage; // Programming Page Size
unsigned long Res; // Reserved for future Extension
unsigned char valEmpty; // Content of Erased Memory
unsigned long toProg; // Time Out of Program Page Function
unsigned long toErase; // Time Out of Erase Sector Function
struct FlashSectors sectors[SECTOR_NUM];
};
从上面的结构体中我们可以知道,我们的flash地址、大小、扇区大小等信息,这些都会在下载bin文件的时候用到。
在FlashDev.c中,我们可以看到16行,表示flash设备的类型,有如下几种情况。
#define UNKNOWN 0 // Unknown
#define ONCHIP 1 // On-chip Flash Memory
#define EXT8BIT 2 // External Flash Device on 8-bit Bus
#define EXT16BIT 3 // External Flash Device on 16-bit Bus
#define EXT32BIT 4 // External Flash Device on 32-bit Bus
#define EXTSPI 5 // External Flash Device on SPI
17--23就是指flash的参数,有多大,每页多少字节等等。
FlashPrg.c里面就只是一些函数的接口,具体有哪些函数接口在下面。
// Flash Programming Functions (Called by FlashOS)
extern int Init (unsigned long adr, // Initialize Flash
unsigned long clk,
unsigned long fnc);
extern int UnInit (unsigned long fnc); // De-initialize Flash
extern int BlankCheck (unsigned long adr, // Blank Check
unsigned long sz,
unsigned char pat);
extern int EraseChip (void); // Erase complete Device
extern int EraseSector (unsigned long adr); // Erase Sector Function
extern int ProgramPage (unsigned long adr, // Program Page Function
unsigned long sz,
unsigned char *buf);
extern unsigned long Verify (unsigned long adr, // Verify Function
unsigned long sz,
unsigned char *buf);
其实就是对flash的操作,以前搞过spi flash(就是w25qxx系列),对上面的回调函数很好理解。
直接将keil官方提供的例子进行编译,如下图。
其他的跟keil生成程序一样,画横线的log是在编译结束之后,将axf拷贝位flx文件,从这里可以看出,
flm文件其实就是axf文件,看看二者的文件大小,均为10500Byte。
axf文件是什么?是在bin文件的基础山添加了地址信息和调试信息的文件。使用记事本打开,确实有调试信息。
能生成axf文件,说明有sct(分散加载文件)指导整个工程,生成axf,我们在keil配置里面找到之后,发现只有一个叫做Target.lin的文件,打开之后看到:
跟平常见到的sct文件不太一样。
其实,本质上并无差别,无非就是指定code中的各种属性(RO、RW、ZI)放到某些位置。
现在来看看FlashPrg.c里面的函数接口。
int Init (unsigned long adr, unsigned long clk, unsigned long fnc)
功能:用于初始化Flash
adr参数表示设备基地址(Base Address)
clk参数表示时钟频率
adr参数表示功能码(1 - 擦除, 2 - 程序, 3 -验证)
int ProgramPage (unsigned long adr, unsigned long sz,unsigned char *buf);
功能:将代码写入到flash中。
adr参数表示flash起始地址
sz参数表示写入数据的大小
buf参数表示缓存区
这里强调一点,falsh是以块为单位组织的,在FlashDev里面也有指定,大小为szPage。主机系统往flash写入数据的时候会确保不会出现跨块写入数据(就是一块一块的写入)。在前面的内容中,
T5324 000:563.106 Data: 88 08 00 20 D5 01 00 10 8B 0A 00 10 65 0A 00 10 ...
T5324 000:563.126 CPU_WriteMem(512 bytes @ 0x20000670)
写入是以512B大小的内容往flash写入。其他的函数都比较好理解,就不细讲了。
回过头来再看jlink下载日志(写入flash的部分已删掉):
T5324 000:386.354 JLINK_WriteMem(0xE0001000, 0x1C Bytes, ...)
T5324 000:386.358 Data: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ...
T5324 000:386.372 CPU_WriteMem(28 bytes @ 0xE0001000)
T5324 000:390.509 JLINK_WriteMem(0x20000000, 0x670 Bytes, ...)
T5324 000:390.517 Data: 00 BE 0A E0 0D 78 2D 06 68 40 08 24 40 00 00 D3 ...
T5324 000:390.720 CPU_WriteMem(1648 bytes @ 0x20000000)
T5324 000:527.583 JLINK_WriteMem(0x20000000, 0x670 Bytes, ...)
T5324 000:527.598 Data: 00 BE 0A E0 0D 78 2D 06 68 40 08 24 40 00 00 D3 ...
T5324 000:527.622 CPU_WriteMem(1648 bytes @ 0x20000000)
.....
T1A34 000:768.192 JLINK_WriteMem(0x20000000, 0x670 Bytes, ...)
T1A34 000:768.209 Data: 00 BE 0A E0 0D 78 2D 06 68 40 08 24 40 00 00 D3 ...
T1A34 000:768.232 CPU_WriteMem(1648 bytes @ 0x20000000)
T1A34 000:884.512 JLINK_WriteMem(0x20000000, 0x2 Bytes, ...)
T1A34 000:884.533 Data: FE E7
T1A34 000:884.552 CPU_WriteMem(2 bytes @ 0x20000000)
以下的部分内容,有些是猜测(根据自己的实践和网上的内容进行合理猜测)。
T5324 000:386.354:跟DWT通信,开始写入flash算法(也就时一段短的程序)。
T5324 000:390.509:将flash写入算法的内容写入到RAM中,地址在这里设置。
之后,通过jlink,按照块(512B)大小发送程序的镜像文件(内容和bin文件一样),由flash写入算法接受数据,将镜像文件写入到flash中。最后完成校验、复位等工作(还有一些工作就不展开了,跟本文内容不是很紧密),jlink就可以断开了。
这部内容简单的总结下就是:jlink先和M3内核沟通,然后将flash下载算法传输到到RAM中,再发送bin文件到RAM中,由flash下载算法将RAM中的bin文件(前面接受到的)写入到内部的flash。最后进行校验,复位(若有)等扫尾工作。
到这里也明白了,下载bin文件到flah中的过程,其实是由我们指定的(flm文件),这个flm文件一般由芯片厂商提供,keil只是将这个接口(具体的下载bin的过程)开放出去了,各个芯片厂家适配他们自己的芯片。
bin下载的过程基本上已经讲完了,如果还有内容的话,以后再更。最后谈一谈芯片如何启动。再说明芯片启动之前,我们先看看芯片的地址空间。
上面时M3内核定义的地址空间使用,一般芯片厂商会根据自己的需要,在划分的区域里面实现自己的地址划分。(博主这里不想找了,就简单的画一个我需要讲解的部分,作为说明素材)
玩过stm32的同学应该知道,stm32有两个boot引脚:boot0、boot1,二者配合起来选择从哪里启动(boot[0:1] 均为0的时候从内部flash启动,也就是从地址0x0800 0000,其他的情况从RAM或者外部flash启动,现在各位同学应该知道如果从外部flash启动该如何做了0^0)。
在配置boot0/1为低电平时,芯片从0x0800 0000地址出读取两个字,一个sp,一个pc,然后配置中断向量表、进入复位中断(就是startup_stm32xxxx.s的内容)。但按理说,pc、sp一开始的值应该为0,那如何跳到0x0800 0000进而读取那里的bin文件呢?问题就在Boot里面
看上图,Boot是一段代码,由厂商烧录。芯片在启动的时候,pc值为0,运行Boot程序,Boot程序会去检测boot0/1引脚,进而决定是从内部flash还是ram或者其他的地方启动(其实就是一个地址的区别,内部flash启动就是0x0800 0000,ram就是0x2000 0000),之后跳转到0x0800 0000(boot0/1为低电平),下面才是startup_stm32xxxx.s的内容。