|
之前都是把程序直接下载到DDR内存,然后直接跳转到内存去运行,之所以可以运行是因为开发板自带的u-boot已经初始化好了DDR内存、时钟等。由于u-boot已经初始化好了时钟,因此这次实验就不能像之前那样操作了,而需要把程序直接烧写到SD卡,然后从SD卡启动。
S5PV210启动流程:
查看S5PV210芯片手册和《S5PV210_iROM_ApplicationNote_Preliminary_20091126》
可以通过配置OM引脚选择如下任意一个设备启动
• General NAND Flash memory
•OneNAND memory
• SD/ MMC memory (such as MoviNAND and iNAND)
•eMMC memory
•eSSD memory
• UART and USB devices
在系统复位时,CPU从固化在片内ROM里的代码开始执行,然而系统复位可能不是在启动时,也可能在被唤醒时,因此IROM Code必须根据复位状态做出适当的处理。
• iROM代码放在片内64KB ROM中。它初始化基本的系统功能,比如时钟,栈,堆。
• iROM代码从从指定的启动设备(NAND/SD/NOR等)加载第1阶段boot loader(BL1)到片内96KB的SRAM。启动设备通过OM引脚选择。
• 第1阶段的boot loader(BL1)加载第2阶段的boot loader(BL2)到片内SRAM
• 第2阶段boot loader(BL2)初始化系统时钟,UART和DRAM控制器。初始化DRAM后,它从启动设备加载操作系统镜像到DRAM。
• 当启动完成后,第2阶段boot loader(BL2)跳转到操作系统去执行。
程序开始于iROM,然后到SRAM,最终程序在DRAM中执行。
iROM(BL0)启动序列如下:
1. 关闭看门狗
2. 初始化指令icache.
3. 初始化栈和堆
4. 初始化块设备拷贝函数
5. 设置时钟分频, 锁定时间, 锁相环(PLL)和时钟源.
6. 检测OM引脚选择从哪个设备启动,然后从启动设备加载BL1(最大16KB)到iRAM
7. 对BL1的校验和进行验证,如果验证失败,iROM将尝试从第2个设备启动
8. 如果是安全模式启动,则对BL1进行完整性验证
9. 跳转到BL1的起始地址(0xD0020010)
iRAM(BL1)启动序列如下:
1. 从启动设备加载BL2(最大80KB)到iRAM
2. 初始化系统时钟,UART,DRAM
3. 从启动设备加载OS到DRAM
4. 跳转到DRAM中的OS执行(0x2000000 或 0x40000000)
iROM在加载BL1时会校验BL1的头信息,规定如下
0x0:BL1的大小(最大16KB - 16B)
0x4 : 0 规定
0x8 : BL1的校验和
0x16 : 0 规定
所以我们在生成led.bin后,还需要添加16B的头信息。
校验和计算方法见《S5PV210_iROM_ApplicationNote_Preliminary_20091126》
[cpp] view plaincopy
01.for(count=0;count< dataLength;count+=1)
02.{
03. buffer = (*(volatile u8*)(uBlAddr+count));
04. checkSum = checkSum + buffer;
05.}
- count unsigned int 类型的变量.
- dataLength unsigned int类型的变量。它包含BL1的大小.
- buffe unsigned short 类型的变量。 它用来从BL1中读取一个字节.
- checksum unsigned int 类型的变量。它包含BL1的和.
注意:《S5PV210_iROM_ApplicationNote_Preliminary_20091126》中有这样一段描述
SD/MMC拷贝函数,我的判断是:在iROM从SD卡加载BL1时,使用的就是这个函数,注意里面的关键参数
param u16 blockSize : Number of blocks to copy.
拷贝多少块,也就是说在iROM从SD卡加载BL1时是按块的整数倍拷贝的,而不是按字节拷贝。再接着看
从这可以看出一个块的大小是512字节,而且第0块保留不用,我们需要将led.bin 从第1块开始烧写。
所以BL1的头信息的BL1的大小(包括头信息16B的大小)应该是512的整数倍。我刚开始没有注意到这点,在制作头信息时按led.bin的实际大小(多少字节)来填充第0字节,导致iROM校验失败,没法启动。
时钟配置:
S5PV210由3个时钟域构成,分别是主系统(MSYS),显示系统(DSYS),外围系统(PSYS)。
1.MSYS域包括Cortex A8核、DRAM内存控制器、3D、iROM、iRAM、INTC等。
2.DSYS域包括显示相关模块,包括FIMC、FIMD、JPEG等。
3.PSYS域用于安全、I/O外围、低功耗的声音播放等。
如下图所示
S5PV210包含4个锁相环(APLL、MPLL、EPLL、VPLL)
手册上建议使用24MHz的晶振为4个PLL提供输入时钟。
在S5PV210中的典型应用:
• Cortex A8 and MSYS clock domain uses APLL (that is, ARMCLK, HCLK_MSYS, and PCLK_MSYS).
• DSYS and PSYS clock domain (that is, HCLK_DSYS, HCLK_PSYS, PCLK_DSYS, and PCLK_PSYS) and
other peripheral clocks (that is, audio IPs, SPI, and so on) use MPLL and EPLL.
• Video clocks uses VPLL.
手册推荐的时钟
•freq(ARMCLK) = 1000 MHz
•freq(HCLK_MSYS) = 200 MHz
•freq(HCLK_IMEM) = 100 MHz
•freq(PCLK_MSYS) = 100 MHz
•freq(HCLK_DSYS) = 166 MHz
•freq(PCLK_DSYS) = 83 MHz
•freq(HCLK_PSYS) = 133 MHz
•freq(PCLK_PSYS) = 66 MHz
• freq(SCLK_ONENAND) = 133 MHz, 166 MHz
PLL:
− APLL 用来驱动 MSYS 域 和 DSYS 域. 它能产生高达 1 GHz的频率
− MPLL用来驱动 MSYS 域和 DSYS 域.它能产生高达 2 GHz的频率
− EPLL 主要用来产生 声音相关的时钟.
− VPLL主要用来产生视频系统操作的时钟, 54 MHz.
− 典型的, APLL 驱动MSYS域,MPLL 驱动DSYS 域.
时钟配置步骤如下:
Turn on a PLL
(A,M,E,V)PLL_CON[31] = 1; // Power on a PLL (Refer to (A, M, E, V) PLL_CON SFR)
wait_lock_time; // Wait until the PLL is locked
(A, M, E, V)PLL_SEL = 1; // Select the PLL output clock instead of input reference clock, after PLL
output clock is stabilized. (Refer to 0, 4, 8, 12th bit of CLK_SRC0 SFR)
Once you turned on any PLL, do not turn off that.
Change PLL’s PMS values
Set PMS values; // Set PDIV, MDIV, and SDIV values
(Refer to (A, M, E, V) PLL_CON SFR)
Change the system clock divider values
CLK_DIV0 [31:0] = target value0;
Change the divider values for special clocks
CLK_DIV1 [31:0] = target value1;
CLK_DIV2 [31:0] = target value2;
代码如下:
start.S
[cpp] view plaincopy
01..global _start /* 声明一个全局的标号 */
02._start:
03. bl clock_init /* 时钟初始化 */
04. bl main /* 跳转到C函数去执行 */
05.halt:
06. b halt
问:为什么start.S中没有像S3C2440那样首先关闭看门狗、为调用C函数设置栈这些操作?
答:因为在iROM里的代码已经帮我们做好了这些,包括基本的时钟初始化。
iROM初始化的时钟配置如下:
clock.c
[cpp] view plaincopy
01.#define APLLCON0 *((volatile unsigned int *)0xE0100100)
02.#define MPLLCON *((volatile unsigned int *)0xE0100108)
03.#define EPLLCON0 *((volatile unsigned int *)0xE0100110)
04.#define VPLLCON *((volatile unsigned int *)0xE0100120)
05.#define CLK_SRC0 *((volatile unsigned int *)0xE0100200)
06.#define CLK_DIV0 *((volatile unsigned int *)0xE0100300)
07.#define CLK_DIV1 *((volatile unsigned int *)0xE0100304)
08.#define CLK_DIV2 *((volatile unsigned int *)0xE0100308)
09.#define CLK_DIV3 *((volatile unsigned int *)0xE010030C)
10.
11.void clock_init()
12.{
13. /* 1、设置PLL_LOCK寄存器(这里使用默认值) */
14. /* 2、设置PLL_CON寄存器(使用芯片手册推荐的值) */
15. APLLCON0 = (1 << 0) | (3 << 8) | (125 << 16) | (1 << 31); /* FOUTAPLL = 1000MHz */
16. MPLLCON = (1 << 0) | (12 << 8) | (667 << 16) | (1 << 31); /* FOUTMPLL = 667MHz */
17. EPLLCON0 = (1 << 0) | (12 << 8) | (667 << 16) | (1 << 31); /* FOUTEPLL = 96MHz */
18. VPLLCON = (3 << 0) | (6 << 8) | (108 << 16) | (1 << 31); /* FOUTVPLL = 54MHz */
19.
20. /* 3、选择PLL为时钟输出 */
21. /* MOUT_MSYS = SCLKAPLL = 1000MHz
22. ** MOUT_DSYS = SCLKMPLL = 667MHz
23. ** MOUT_PSYS = SCLKMPLL = 667MHz
24. */
25. CLK_SRC0 = (1 << 0) | (1 << 4) | (1 << 8) | (1 << 12);
26.
27. /* 4、设置系统时钟分频值 */
28. /* freq(ARMCLK) = MOUT_MSYS / (APLL_RATIO + 1) = 1000MHz / (0 + 1) = 1000MHz
29. ** freq(HCLK_MSYS) = ARMCLK / (HCLK_MSYS_RATIO + 1) = 1000MHz / (4 + 1) = 200MHz
30. ** freq(PCLK_MSYS) = HCLK_MSYS / (PCLK_MSYS_RATIO + 1) = 200MHz / (1 + 1) = 100MHz
31. ** freq(HCLK_DSYS) = MOUT_DSYS / (HCLK_DSYS_RATIO + 1) = 667 / (3 + 1) = 166MHz
32. ** freq(PCLK_DSYS) = HCLK_DSYS / (PCLK_DSYS_RATIO + 1) = 166 / (1 + 1) = 83MHz
33. ** freq(HCLK_PSYS) = MOUT_PSYS / (HCLK_PSYS_RATIO + 1) = 667 / (4 + 1) = 133MHz
34. ** freq(PCLK_PSYS) = HCLK_PSYS / (PCLK_PSYS_RATIO + 1) = 133 / (1 + 1) = 66MHz
35. */
36. CLK_DIV0 = (0 << 0) | (4 << 8) | (1 << 12) | (3 << 16) | (1 << 20) | (4 << 24) | (1 << 28);
37.}
led.c
[cpp] view plaincopy
01.#define GPC0CON *((volatile unsigned int *)0xE0200060)
02.#define GPC0DAT *((volatile unsigned int *)0xE0200064)
03.
04.void delay(volatile unsigned int t)
05.{
06. volatile unsigned int t2 = 0xFFFF;
07. while (t--)
08. for (; t2; t2--);
09.}
10.
11.int main()
12.{
13. int toggle = 0;
14. GPC0CON &= ~(0xFF << 12);
15. GPC0CON |= 0x11 << 12; // 配置GPC0_3和GPC0_4为输出
16.
17. while (1)
18. {
19. GPC0DAT &= ~(0x3 << 3); // 熄灭LED1和LED2
20.
21. if (toggle)
22. GPC0DAT |= 1 << 3; // 点亮LED1
23. else
24. GPC0DAT |= 1 << 4; // 点亮LED2
25.
26. toggle = !toggle;
27. delay(0x50000);
28. }
29.
30. return 0;
31.}
Makefile
[cpp] view plaincopy
01.led.bin: start.o clock.o led.o
02. arm-linux-ld -Ttext 0xD0020010 -o led.elf $^
03. arm-linux-objcopy -O binary led.elf $@
04. arm-linux-objdump -D led.elf > led.dis
05.
06.%.o : %.c
07. arm-linux-gcc -c $< -o $@
08.%.o : %.S
09. arm-linux-gcc -c $< -o $@
10.
11.clean:
12. rm *.o *.elf *.bin *.dis
执行make后生成led.bin
addheader.c用于构造带有头信息的bin文件
[cpp] view plaincopy
01./*
02.** 在BL0阶段,iROM内固化的代码读取nandflash或SD卡前面最大16K的内容(即BL1)到iRAM,
03.** 并比对前16字节中的校验和是否正确,正确则继续,错误则尝试启动下一个设备。
04.** BL1的头信息规定如下
05.** 0x0:BL1的大小(最大16K,包括BL1头信息的大小)
06.** 0x4: 0(规定)
07.** 0x8:校验和
08.** 0xC:0(规定)
09.*/
10.#include <stdio.h>
11.#include <string.h>
12.#include <stdlib.h>
13.
14.#define IMG_SIZE (16*1024)
15.#define HEADER_SIZE 16
16.#define BLKSIZE 512
17.
18.int main (int argc, char *argv[])
19.{
20. FILE *fp;
21. unsigned char *Buf;
22. int BufLen;
23. int nbytes, fileLen;
24. unsigned int checksum, count;
25. int i;
26.
27. if (argc != 3)
28. {
29. printf("Usage: %s <source file> <destination file>\n", argv[0]);
30. return -1;
31. }
32.
33. /* 分配16K的buffer */
34. BufLen = IMG_SIZE;
35. Buf = malloc(BufLen);
36. if (!Buf)
37. {
38. perror("Alloc buffer failed!");
39. return -1;
40. }
41. memset(Buf, 0x00, BufLen);
42.
43. /* 读源bin到buffer */
44. fp = fopen(argv[1], "rb");
45. if( fp == NULL)
46. {
47. perror("source file open error");
48. free(Buf);
49. return -1;
50. }
51. /* 获取源bin长度 */
52. fseek(fp, 0L, SEEK_END);
53. fileLen = ftell(fp);
54. fseek(fp, 0L, SEEK_SET);
55.
56. /* 源bin长度不得超过16K-16byte */
57. fileLen = (fileLen < (IMG_SIZE - HEADER_SIZE)) ? fileLen : (IMG_SIZE - HEADER_SIZE);
58.
59. /* 读源bin到buffer[16] */
60. nbytes = fread(Buf + HEADER_SIZE, 1, fileLen, fp);
61. if (nbytes != fileLen)
62. {
63. perror("source file read error\n");
64. free(Buf);
65. fclose(fp);
66. return -1;
67. }
68. fclose(fp);
69.
70. /* 计算校验和 */
71. for(i = 0, checksum = 0; i < fileLen; i++)
72. checksum += Buf[HEADER_SIZE + i];
73.
74. /* 计算BL1的大小:
75. ** BL1的大小包括BL1的头信息
76. ** 另外iROM从SD卡拷贝是按块拷贝的,因此这里需要调整大小为512字节的整数倍
77. */
78. fileLen += HEADER_SIZE;
79. count = fileLen / BLKSIZE * BLKSIZE;
80. if (count < fileLen)
81. count += BLKSIZE;
82. memcpy(Buf, &count, 4); // 保存BL1的大小到Buf[0-3]
83.
84. // 将校验和保存在buffer[8~15]
85. memcpy(Buf + 8, &checksum, 4);
86.
87. fp = fopen(argv[2], "wb");
88. if (fp == NULL)
89. {
90. perror("destination file open error");
91. free(Buf);
92. return -1;
93. }
94. // 将count + HEADER_SIZE字节的buffer拷贝到目的bin中
95. nbytes = fwrite(Buf, 1, count, fp);
96. if (nbytes != count)
97. {
98. perror("destination file write error");
99. free(Buf);
100. fclose(fp);
101. return -1;
102. }
103.
104. free(Buf);
105. fclose(fp);
106.
107. return 0;
108.}
执行如下命令生成addheader
# gcc addheader.c -o addheader
然后再用addheader制作带有头信息的bin文件
# ./addheader led.bin 210.bin
然后将SD卡插入SD读卡器,将鼠标移到虚拟机中,然后将SD卡读卡器插入电脑,这是ubuntu中将自动挂载SD卡
在ubuntu中执行df查看分区信息
root@zjh:/mnt/hgfs/E/cloud/embedded/my_code/tq210# df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sda1 39544232 10202800 27332652 28% /
none 506996 260 506736 1% /dev
none 512616 164 512452 1% /dev/shm
none 512616 372 512244 1% /var/run
none 512616 0 512616 0% /var/lock
.host:/ 331099132 158170568 172928564 48% /mnt/hgfs
/dev/sdd 46220 46220 0 100% /media/XIAOMI
/dev/sdb1 3864000 4 3863996 1% /media/720C-93BE
sdb1就是我们的SD卡的第1个分区
执行如下命令烧写210.bin到SD的第1个块
# dd bs=512 iflag=dsync oflag=dsync if=210.bin of=/dev/sdb seek=1
1+1 records in
1+1 records out
528 bytes (528 B) copied, 0.0110204 s, 47.9 kB/s
bs指定一次烧写多少字节
dsync表示为数据使用同步I/O
if指定输入文件
of指定输出设备
seek指定从第几块开始烧写
然后拔下SD卡,将其插入的TQ210开发板,然后拨动启动选择开关,选择从SD启动
然后上电可以看到LED交替闪烁,可以去掉start.S中的时钟初始化,再次编译程序,烧写,可以看出LED闪烁变慢了。
[cpp] view plaincopy
01..global _start /* 声明一个全局的标号 */
02._start:
03. //bl clock_init /* 时钟初始化 */
04. bl main /* 跳转到C函数去执行 */
05.halt:
06. b halt
转载请注明来源:http://blog.csdn.net/zjhsucceed_329/
|
|