开发板——在X210开发板上进行裸机开发的细节(总结篇)

 以下内容是学习裸机开发过程中的一些细节内容的记录。

1、汇编语言函数细节

用汇编写的函数,末尾应该添加 “mov pc,lr” 语句。

2、裸机代码相关文件

如下图所示,裸机代码包括以下文件:

start.S文件:进行基本的设置以及流程控制,如关看门狗、初始化时钟、设置SVC栈等设置,以及跳转到其他文件中(某些.S、.c文件)的函数。

Makefile文件:负责指导编译。

mkv210_image.c文件:负责添加16字节的校验头,把usb启动的文件转化为sd卡启动的文件。

write2sd文件:负责将生成的文件烧写进sd卡的第一扇区开始的区域。 

3、关于链接地址

如下图所示,链接地址可以用“-Tlink.lds”或者“-Ttext xxx”这种方式指定。 

4、关于重定位的理解

(1)在sram内部重定位

根据S5PV210地址映射,第3点提到的链接脚本所指定的链接地址0xd0024000是在SRAM中,这是在sram内部重定位,因此不需要初始化DDR。

/*
 * 描述:	演示重定位(在SRAM内部重定位)
 */

#define WTCON		0xE2700000
#define SVC_STACK	0xd0037d80

.global _start	// 把_start链接属性改为外部,这样其他文件就可以看见_start了

_start:
	
	// 第1步:关看门狗(向WTCON的bit5写入0即可)
	ldr r0, =WTCON
	ldr r1, =0x0
	str r1, [r0]
	
	// 第2步:设置SVC栈
	ldr sp, =SVC_STACK
	
	// 第3步:开/关icache
	mrc p15,0,r0,c1,c0,0;			// 读出cp15的c1到r0中
	//bic r0, r0, #(1<<12)			// bit12 置0  关icache
	orr r0, r0, #(1<<12)			// bit12 置1  开icache
	mcr p15,0,r0,c1,c0,0;
	
	// 第4步:重定位。(这里的代码细节说明adr是与运行相关的,ldr是与链接相关的。)
	adr r0, _start     // adr指令用于加载_start当前运行地址             // adr加载时就叫短加载	 			
	ldr r1, =_start    // ldr指令用于加载_start的链接地址:0xd0024000    // ldr加载时如果目标寄存器是pc就叫长跳转,如果目标寄存器是r1等就叫长加载
	
	// bss段的起始地址
	ldr r2, =bss_start	// 就是我们重定位代码的结束地址,重定位只需重定位代码段和数据段即可
	
	
	cmp r0, r1			// 比较_start的运行时地址和链接地址是否相等
	beq clean_bss		// 如果相等说明不需要重定位,所以跳过copy_loop,直接到clean_bss
						// 如果不相等说明需要重定位,那么直接执行下面的copy_loop进行重定位
						// 重定位完成后继续执行clean_bss。

// 用汇编来实现的一个while循环
copy_loop:
	ldr r3, [r0], #4    // 源
	str r3, [r1], #4	// 目的   这两句代码就完成了4个字节内容的拷贝
	cmp r1, r2			// r1和r2都是用ldr加载的,都是链接地址,所以r1不断+4总能等于r2
	bne copy_loop

// 清bss段,其实就是在链接地址处把bss段全部清零
clean_bss:
	ldr r0, =bss_start					
	ldr r1, =bss_end
	
	cmp r0, r1				// 如果r0等于r1,说明bss段为空(即不存在bss段),直接下去
	beq run_on_dram			// 清除bss完之后的地址
	
	mov r2, #0

clear_loop:
	str r2, [r0], #4		// 先将r2中的值放入r0所指向的内存地址(r0中的值作为内存地址),
	cmp r0, r1				// 然后r0 = r0 + 4
	bne clear_loop

run_on_dram:	
	// 长跳转到led_blink开始第二阶段
	ldr pc, =led_blink				// ldr指令实现长跳转
	
	//bl led_blink					// bl指令实现短跳转
	
// 汇编最后的这个死循环不能丢
	b .

(2)重定位至DDR

如果链接脚本指定链接地址为0x2000 0000,而这个地址位于DDR中,则需要重定位至DDR中,因此需要初始化DDR。如下所示,重定位至DDR只是多了一个内存初始化操作而已。

/*
 * 文件名:	led.s	
 * 作者:	朱老师
 * 描述:	演示重定位
 */

#define WTCON		0xE2700000
#define SVC_STACK	0xd0037d80

.global _start	
_start:
	//..............

	// 第4步:初始化ddr
	bl sdram_asm_init //此函数末尾记得添加mov pc,lr
	
	// 第5步:重定位,后面的代码和之前的完全一样。故不写。

5、SRAM的地址0xd002_0010映射到0x0000_0000地址

Makefile用 -Ttext 0x0 指定链接地址为0x0。这意味着我们认为这个程序将来会放在0x0地址中运行,但实际上运行时的地址是0xd0020010(我们用dnw下载时指定的下载地址)。

这两个地址看似不同但实际相同,因为S5PV210内部把SRAM的地址0xd002_0010映射到地址0x0000_0000。把链接地址设为0x0000_0000,就等价于链接地址是0xd002_0010。

BL0执行完后会自动跳转到0xd0020010这个地址开始运行,这个地址是CPU设计决定的。

6、S5PV210内置的拷贝函数

S5PV210的地址映射可知,0xD0037F98这个地址是S5PV210内置的拷贝函数的入口地址。

这个拷贝函数用于从sd卡或者inand中拷贝数据到DDR中,具体的代码细节我们不得而知,只知道它的入口地址和参数含义。该函数的参数1是从哪个通道拷贝(SD0还是SD2,对应着inand和SD卡),参数2是从哪个扇区开始拷贝,参数3是拷贝多少个扇区,参数4是拷贝到哪里(一个具体的地址,这个地址可能在SRAM中,也可能在DDR中)。

BL0内部利用这个函数,将16KB(由参数3决定)的BL1,从SD卡或者inand(由参数1决定)的第一个扇区(由参数2决定),拷贝到 SRAM 的 0xd002_0010 地址(由参数4决定)。由于BL0是写死在IROM中的,在BL0阶段我们唯一能做的就是通过启动介质拨码开关来决定参数1,参数2、3、4固定的。

而BL1中如果需要拷贝SD卡中的某些内容,则还是使用S5PV210内置的拷贝函数,只不过因为BL1是我们编写的,我们可以自由设置参数1,2,3,4。比如将一个大文件分成两个文件BL1和BL2,先将BL1烧写至SD卡第1扇区,BL2烧写至SD卡合适的扇区位置(假如烧录至第49扇区)。

上电后IROM中的BL0将利用拷贝函数,把BL1从SD卡第1扇区拷贝到 SRAM的 0xd002_0010地址来执行。BL1在执行时也利用拷贝函数,将BL2从SD卡的第49扇区拷贝到一个合适的地址(这个地址可能位于SRAM或者DDR中,它是由链接脚本规定的,我们从链接脚本得知这个地址,然后作为参数4传入拷贝函数),最后BL1代码末尾处执行跳转语句,跳转到这个地址,就可以接着执行BL2。

7、验证6中所讲述的内容

(1)BL1的相关内容

首先,BL1的链接脚本的链接地址是0xd0020010,如下所示。这就很合理,这个地址就是BL1该呆在的地方,因为CPU设计时规定的一开始运行的地址就是0xd0020010。

SECTIONS
{
	. = 0xd0020010;
	
	.text : {
		start.o
		sdram_init.o
		* (.text)
	}
    		
	.data : {
		* (.data)
	}
	
	bss_start = .; 
	.bss : {
		* (.bss)
	}
	
	bss_end  = .;	
}

其次,BL1的Makefile中,需要把BL1做16字节填充的,这也很合理。

接着,start.S中初始化DDR后,利用拷贝函数把存储在SD卡中的BL2,复制到DDR的某个位置,并跳转到该位置执行BL2,如下所示:

#define WTCON		0xE2700000
#define SVC_STACK	0xd0037d80

.global _start	// 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
	// 第1步:关看门狗(向WTCON的bit5写入0即可)
	ldr r0, =WTCON
	ldr r1, =0x0
	str r1, [r0]
	
	// 第2步:设置SVC栈
	ldr sp, =SVC_STACK
	
	// 第3步:开/关icache
	mrc p15,0,r0,c1,c0,0;			// 读出cp15的c1到r0中
	//bic r0, r0, #(1<<12)			// bit12 置0  关icache
	orr r0, r0, #(1<<12)			// bit12 置1  开icache
	mcr p15,0,r0,c1,c0,0;

	// 第4步:初始化ddr
	bl sdram_asm_init

	// 第5步:重定位,从SD卡第45扇区开始,复制32个扇区内容到DDR的0x23E00000
	bl copy_bl2_2_ddr
		
    // 汇编最后的这个死循环不能丢
	b .
#define SD_START_BLOCK	45
#define SD_BLOCK_CNT	32
#define DDR_START_ADDR	0x23E00000 //这地址在DDR中,不在IRAM中

typedef unsigned int bool;

// 通道号:0或者2
// 开始扇区号:这里设为45
// 读取扇区个数:这里设备32
// 读取后放入的地址:这里设为0x23E00000
// with_init:0
typedef bool(*pCopySDMMC2Mem)(int, unsigned int, unsigned short, \
                                                    unsigned int*, bool);
typedef void (*pBL2Type)(void);


//从SD卡第45扇区开始,复制32个扇区内容到DDR的0x23E00000,然后跳转到23E00000去执行
void copy_bl2_2_ddr(void)
{
	// 第一步,读取SD卡扇区到DDR中
    //定义一个函数指针并指向拷贝函数
	pCopySDMMC2Mem p1 = (pCopySDMMC2Mem)0xD0037F98);
    //给(指向拷贝函数的)函数指针传入参数,开始读取SD卡到DDR中
	p1(2, SD_START_BLOCK, SD_BLOCK_CNT, (unsigned int *)DDR_START_ADDR, 0);	
	
	// 第二步,跳转到DDR中的BL2去执行
    //定义一个函数指针并指向BL2的下载地址
	pBL2Type p2 = (pBL2Type)DDR_START_ADDR;
    //通过函数指针这种方式跳转到DDR中的BL2的下载地址,去执行BL2数
	p2();
}

(2)BL2的相关内容

首先,BL2的链接地址应该由BL1的拷贝函数将BL2拷贝到哪里决定。由BL1代码可知BL2被拷贝到了0x23E00000,那么BL2的链接地址应该是0x23E00000。

另外,BL2有main函数文件、start.S文件等文件,怎么知道先执行start.S文件呢?这是由链接脚本中的.o文件的顺序决定的。

SECTIONS
{
	. = 0x23E00000;
	
	.text : {
		start.o
		* (.text)
	}
    		
	.data : {
		* (.data)
	}
	
	bss_start = .; 
	.bss : {
		* (.bss)
	}
	
	bss_end  = .;	
}

其次,BL2的makefile中不会再添加16字节填充的操作,经查验果真如此。

BL2的start.S文件内容如下,可知接下来会到main函数的文件中执行main函数。

#define WTCON		0xE2700000
#define SVC_STACK	0xd0037d80

.global _start	

_start:
	ldr pc, =main				// ldr指令实现长跳转
	
// 汇编最后的这个死循环不能丢
	b .
	

(3)总结

BL1所使用的拷贝函数还是BL0所使用的拷贝函数,即S5PV210内置的拷贝函数。

BL2的入口地址,是BL2的链接脚本指定链接地址,它由BL1将BL2拷贝到哪里决定。

BL2链接脚本中.o的顺序,决定了BL2众多的程序文件中,先执行哪些文件。

8、将数据烧写至SD卡的方式

方式1:在linux中利用write2sd文件

write2sd文件内容如下,可知BL1和BL2分别烧录至第1扇区、第45扇区开始的地方。

#!/bin/sh
sudo dd iflag=dsync oflag=dsync if=./BL1/BL1.bin of=/dev/sdb seek=1
sudo dd iflag=dsync oflag=dsync if=./BL2/BL2.bin of=/dev/sdb seek=45

方式2:利用九鼎提供的烧写软件

它只能将一个文件(uboot.bin或者裸机镜像文件)烧写至SD卡的第1扇区开始的地方。

注意,它只能烧写一个文件,而且必须烧写至SD卡第1扇区。

方式3:利用三星提供的sd_fusing文件夹

具体见利用三星提供的sd_fusing.sh将uboot烧写到SD卡

这个文件夹是将uboot.bin烧写至sd的方法,特指烧写uboot镜像,而非其他的?(错误的认识,可以烧写其他镜像,但得16字节的校验头。)

补充说明

其实方式3也是利用方式1作为方式3的最后一个步骤。

方式3前面还包括将sd卡分区、截取uboot.bin前8k作为BL1(而整个uboot.bin作为BL2)等步骤。

方式3将sd卡分区的意义何在呢?sd卡不是本身就已经分区了吗?(待解决)

9、裸机代码中的前几个固定步骤

裸机代码的前几个步骤比较固定,都是一些初始化操作,而且有些操作在BL0中已经完成,这里重新再设置一遍也不会出错。另外,ddr的初始化和重定位这两个步骤看情况是否需要。

(1)开发板制锁

如果不进行这个步骤,得一直按着POWER键。

	// 第0步:开发板置锁
	ldr r0, =0xE010E81C
	ldr r1, [r0]
	ldr r2, =0x301
	orr r1, r1, r2
	str r1, [r0]

(2)关看门狗

开启看门狗是为了防止机器故障时自动复位。如果开启看门狗后,没有及时去喂狗,则系统会自动复位。没有操作系统前,喂狗这个操作需要编写代码完成,因此为了减少编码工作,这里选择直接把看门狗关闭。(其实BL0已经关了看门狗,这里重新设置一遍。)

    // WTCON(0xE2700000),其中bit5是看门狗的开关:0代表关,1代表开
	// 第1步:关看门狗(向WTCON的bit5写入0即可)
	ldr r0, =WTCON
	ldr r1, =0x0
	str r1, [r0]

(3)初始化时钟

这一部分的内容,见博客:S5PV210的时钟系统_天糊土的博客-CSDN博客

	// 第2步:初始化时钟
	bl clock_init

(4)设置SVC栈

C语言的运行需要一定的条件,这些条件由汇编来提供。C语言运行时主要是需要栈。比如C语言中的局部变量都是用栈来实现的。如果汇编部分没有给C部分设置合理的栈地址,那么C代码中定义的局部变量就会落空,整个程序就死掉了。

我们现在要设置栈,不可能也没有必要去设置所有的栈,我们先要设置自己的模式,然后设置自己的模式下的栈到合理合法的位置即可。

我们如何访问SVC模式下的SP呢?很简单,先把模式设置为SVC,再直接操作SP。但是因为我们复位后就已经是SVC模式了,所以直接设置SP即可。

由于栈必须是当前一段可用的内存(这段内存必须已经初始化,而且这段内存只会被我们用作栈,不会被其他程序征用),当前CPU刚启动时,外部的SDRAM尚未初始化,目前可用的内存只有内部的SRAM(因为它不需初始化即可使用)。因此只能在SRAM中找一段内存来作为SVC的栈。结合iROM_application_note中的memory map,可知SVC栈应该设置为0xd0037D80。

	// 第3步:设置SVC栈
	ldr sp, =SVC_STACK //#define SVC_STACK	0xd0037d80

(5)开关icache

iROM中的BL0已经打开了icache,我们这里重新设置一遍也没有错。

	// 第4步:开/关icache
	mrc p15,0,r0,c1,c0,0;			// 读出cp15的c1到r0中
	//bic r0, r0, #(1<<12)			// bit12 置0  关icache
	orr r0, r0, #(1<<12)			// bit12 置1  开icache
	mcr p15,0,r0,c1,c0,0;

寄存器和SDRAM之间的读取速度差异太大,SDRAM的读取速度远不能满足寄存器的需要,没有cache的话,会拉低系统的整体速度。210内部有32KB icache和32kb dcache,icache是用来缓存指令的;dcache是用来缓存数据的。

(6)DDR的初始化

具体内容见博文S5PV210——SDRAM的初始化

(7)代码的重定位

具体内容见博文重定位的简介与操作

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天糊土

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值