主流外存储设备
我们一般把RAM叫做内存,ROM叫做外存,常见的外部存储器有:
- 磁带,CD,软盘,硬盘,光盘等磁性存储设备,技术成熟,价格便宜,但是读写速度和可靠性不足,桌面电脑使用较多,嵌入式设备一般不采用
- Flash存储设备:使用电学原理存储,没有物理动作,损耗小,读写速度较快,在嵌入式设备中应用较多
- 纯粹测NandFlash,NorFlash,出现的最早,最原始的Flash颗粒,对存储单元做了最基本的存储接口,要求Soc提供Flash读写控制器来读写Flash,但是读写接口的时序比较复杂,内部没有坏块处理机制,需要Soc来处理坏块,而且各个厂家的Flash接口不一致,NandFlash分为SLC和MLC两种,SLC存储比较可靠,但是容量小,MLC可靠性差,但是容量大且比较便宜,是主流的NandFlash技术,
- SD,MMC,MicroSD,TF卡,本质上都是Flash颗粒,但是增加了统一的封装和接口标准,例如SD卡都是遵守了SD卡标准来实现的,这样不同厂家的SD卡就可以通用了
- iNand,MoviNand,eSSD,本质还是NandFlash,综合了SD卡和原始芯片的优势,内部集成的有块设备管理单元,可以进行坏块管理
- SSD,和eSSD一样,只不过做成了硬盘从而可以插进硬盘插槽中
- -
SD卡
MMC卡标准比SD卡要早,所以SD卡兼容MMC卡,MicroSD卡和SD卡相比,只是体积不同,其他方面都一样,TF卡是MicroSD卡的另外一个名称,不同之处在于SD卡有写保护开关,但是TF卡没有
SD卡编程接口
SD卡物理接口
SD卡物理接口有9个针脚与外界连接,其中有两个地,一个电源以及六个信号线
SD卡通信协议
SD卡与SRAM,DDR和SROM是不同的,后者是总线式的访问,只要连接初始化后可以直接以地址方式开始访问,但是SD卡不能直接通过地址访问,需要按照一定的读写时序来访问,SD卡虽然只有一组物理接口,但是支持两种访问协议:
- SD协议:专门用于SD卡通信的协议,速度较快,一般要求Soc中有SD控制器,主频有一定的要求,不能太低,
- SPI协议:在单片机中广泛使用的一种通信协议,SD卡支持使用SPI协议,速度相对SD协议来说速度较低,接口操作时序比较简单,常用于单片机环境
MMC控制器
SD卡内部除了存储单元外还有管理模块,Soc和SD卡通过九针的引脚通过SD或者SPI协议向SD卡管理模块发送命令,时钟,数据信息,然后SD卡返回数据信息,工作时每个任务都需要一定的时序
SD卡启动
Soc通常会支持SD卡启动来扩大适用范围,SD卡启动可以在不借用烧录工具(Jlink)的前提下对SD卡进行刷机,刷完插入卡槽即可启动,可以使用SD卡启动来量产刷机
SD卡需要时序访问,CPU不能直接访问SD卡,但是可以以总线式访问SRAM和DRAM,210中96KB的SRAM和iROM代码作为BL0,由BL0启动BL1
210开发板启动流程
210首先执行内部的iROM(BL0),BL0判断OMPIN决定从哪里启动,如果是从SD卡启动,则BL0读取SD卡前16KB到SRAM中执行(BL1),BL1执行之后就开始执行软件
当镜像小于16KB时,相当于整个镜像作为BL1被加载到SRAM中执行,当镜像大于16KB的时候,需要把镜像分为两部分,第一部分16KB,剩余大小作为第二部分,第一部分作为BL1启动来初始化DRAM并把第二部分加载到DRAM中执行
iROM读取SD卡
iROM中事先内置了代码来初始化外部SD卡或者NandFlash,并且内置了读取SD卡或者NandFlash的代码,BL0就是调用这些device copy function来读取SD卡或者NandFlash中的BL1,
扇区和块
早期的磁存储设备就是块设备,这种设备的存储单元不是以字节为单位,而是以扇区为单位,读写最小单元是扇区,一个扇区有多个字节,一般是512byte,还有1024,2048以及4096byte等,一个扇区可以看成是一个块,磁盘和Flash以块为单位来读写,所以启动时的device copy function就只能以整块为单位读写SD卡
指针函数调用
使用宏定义方式
#define CopySDMMCtoMem(z,a,b,c,e)(((bool(*)(int, unsigned int, unsigned short, unsigned int*, bool))(*((unsigned
int *)0xD0037F98)))(z,a,b,c,e))
函数指针方式
typedef unsigned int bool;
typedef (bool(*pCopySDMMC2Mem)(int, unsigned int, unsigned short, unsigned int*, bool);
// 在实际使用的时候
pCopySDMMC2Mem p1 = (pCopySDMMC2Mem)0xd0037f98;
p1(x,x,x,x,x);
启动代码编写
将代码分为两部分,第一部分16KB,第二部分剩余大小,iROM会自动读取BL1到SRAM执行,初始化DDR并将BL2读取到DDR,BL1长跳转到BL2中执行
程序分为BL1和BL2两个文件夹分别进行处理,BL1将来会下载到SRAM的0xD0020010中执行的,所以BL1需要完成的事情:
- 关看门狗
- 设置栈
- 关iCache
- 初始化DDR
- 从SD卡复制BL2到DDR特定位置
- 跳转执行
三星官方BL1在SD卡中必须从block1开始,长度为小于等于16KB,最大为32个block扇区,BL1为了安全则从block45开始,长度根据实际的BL2大小来分配,DDR一旦初始化好整个空间都是可用的,从中选择一段足够BL2的空间进行复制即可,我们暂时选择0x23E00000,因为BL1只初始化了DDR1,地址范围是0x20000000到0x2FFFFFFF
启动代码
#define SD_START_BLOCK 45
#define SD_BLOCK_COUNT 32
#define DDR_START_ADDR 0x23E00000
typedef unsigned int bool;
// 通道号:0或者2
// 开始扇区号:45
// 读取扇区个数:32
// 读取后放入内存的地址:0x23E00000
// 是否初始化:0
typedef bool (*pCopySDMMC2Mem)(int, unsigned int, unsigned short, unsigned int*, bool);
typedef void (*pBL2Start)(void);
// 从SD卡第45扇区复制32block到DDR的0x23E00000
void copybl2_2ddr() {
// 读取BL2到DDR
pCopySDMMC2Mem p1 = (pCopySDMMC2Mem)(*(unsigned int *)0xd0037f98);
p1(2,SD_START_BLOCK,SD_BLOCK_COUNT,(unsigned int*)DDR_START_ADDR,0);
// 长跳转执行,由于两个程序是独立分开链接的,所以不能使用ldr pc进行跳转,只能使用地址强制跳转
pBL2Start p2 = (pBL2Start)DDR_START_ADDR;
p2();
}
Uboot中的做法
我们的代码完全分为两部分,编写和组织代码比较麻烦,且无法兼容其他启动方式,在Uboot中程序是一整个部分,BL1和BL2在一起,BL1读取的时候读取的是整个程序到DDR中,然后从DDR中利用长跳转(ldr pc, =ledblink)来执行,Uboot编译完成由200多K,前16K为BL1,后面为BL2,烧录的时候先截取uboot.bin前16K(实际是8K)烧录到block1-32中,然后将整个uboot.bin烧录到SD卡的某个扇区中(例如49),实际Uboot从SD卡启动时iROM执行完之后从SD卡读取16K到SRAM执行,初始化DDR并复制49扇区以后的程序到DDR中的启动位置,BL1进行ldr pc, =ledblink跳转到DDR上继续执行。这种方式不需要写成两个独立的程序,能够兼容各种启动方式