【笔记】ARM裸机程序开发_part2

四、GPIO和LED
4.1usb配置DNW启动

DNW驱动安装需要数字签名,我们装好驱动后,连接USB线,配置DNW
下载地址是0xd0020010(这个地址是BL1的地址,约定好辣~)
按住power,DNW中选择Transmit,下载要装载的裸机程序

其原理也就是说,我们把裸机程序当成了BL1,上电后运行BL1也就是运行了写好的裸机程序。

关于0xd0020010的由来,参考官方的iROM手册

4.2 SD跑裸机程序

首先,在uboot下把uboot擦除(在linux或android下)
(help movi)

movi u-boot 0x30000000  (0x3000 0000往后是一段全0)

然后windows下,使用x210_fusing_tool烧写led.bin进去,从SD卡启动

优劣比较:usb调试比较简单,SD调试要拿来拿去比较麻烦。

====================================================

4.3安装交叉工具链

安装软件的方法:
Windows下:.msi .exe等安装文件。下一步一直到安装完毕,桌面上快捷方式等,平时使用快捷方式来启动和调用
绿色软件(免安装软件)不需要安装,直接解压使用exe执行
linux下: linux中装软件比较复杂。一般有以下几种方法
1、在线安装:Ubuntu中使用apt-get install vim安装vim软件
2、自己下载安装包来安装。(较为不妥当的方法)
linux的版本太零散了,兼容成为很大的问题呢。不知道安装包是否与系统匹配。Windows的版本控制非常好,相比于linux是非常方便的
linux版本和软件安装包不熟悉将会非常麻烦!
3、源代码安装(最装逼的方式,是linux独有的方式)
获取源代码之后,现场编译,现场安装。就用gcc编译和运行。甚至可以对软件本身进行修改
总结:安装交叉编译工具链(arm-linux-gcc)实际采用第二种

交叉编译工具链的选择
A盘:toolchain 我们用arm-2009q3。三星官方就使用这个进行开发,避免节外生枝
要求:尽量和我们所使用 的目标平台尽量去匹配。

linux中的目录性质其实都是一样 的,一般装到 根目录下的 【 /usr/local/arm下 】
windows装一般是装program filesx86/x64里面。linux下,原则上是放在哪都是可以的,但是将来程序可能不好找。所以大家总结了一个文件放置的一般定义
/bin 系统自带的应用程序 ls
/sbin 系统自带的系统管理的应用程序 fdisk(磁盘格式化)
/usr 用户自己的程序
注意:ls移动到/sbin目录下,照样是可以用的,但是不好找
我们装软件一般在/usr目录下

步骤1 使用secureCRT(自己的办法),rz 选择文件arm-2009q3.tar.bz2 ,发送到/usr/local/arm下
步骤2 tar -jxcf arm-2009q3.tar.bz2
步骤3 安装完成
步骤4 测试:到真正的安装目录下/usr/local/arm/arm-2009q3/bin 执行arm-linux-gcc -v
./arm-none-linux-gnueabi-gcc -v 打印应用程序 的版本号
测试结果
Thread model: posix
gcc version 4.4.1 (Sourcery G++ Lite 2009q3-67

使用交叉编译工具链编译
/usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-gcc a.c
这样做的缺陷就是因为这个路径太长了,更希望可以 arm-none-linux-gnueabi-gcc a.c
这就需要环境变量

环境变量:就是操作系统的全局变量,对操作系统来说,这个变量的名字和意义都是唯一的
linux可以有很多个环境变量。一些是系统自己定义的,还有一部分是用户扩充的。
我们这里涉及的环境变量是PATH,是系统自定义 的,含义是系统在查找可执行程序时会搜索的路径
打印环境变量信息 : echo PATHexportPATH=/usr/local/arm/arm2009q3/bin: PATH
导出PATH,后加上我们工具链的 路径,方便直接 调用arm-linux-gcc这个应用程序
但问题是,我们关闭一次Terminal之后就不能用了。因为export只是导入一次窗口,再打开就没了
那么就需要:在当前用户(root)的宿主目录下,有一个.bashrc中,添加export PATH =/usr/local/arm/arm-2009q3/bin: PATHcd /.bashrclsa//lsvi.bashrcexportPATH=/usr/local/arm/arm2009q3/bin: PATH

.bashrc这个文件会在每次打开终端时附带执行。对终端来说好像win的“启动”

创建符号链接:root@KimonoYan-virtual-machine:/usr/local/arm/arm-2009q3/bin# 目录下
ln arm-none-linux-gnueabi-gcc -s arm-linux-gcc
ls后可以看到一个蓝色的arm-linux-gcc,这只是个障眼法,实际还是指向的arm-none-linux-gnueabi-gcc这个程序在工作,好像是他的快捷方式

linux下执行脚本命令:source xxx.sh

=============================================

4.4Makefile

注意
创建一个Makefile

    touch makefile(Makefile)
    vi makefile 

编辑

all:
    gcc a.c b.c c.c d.c -o build    //注意必须是tab空格,8个空格会报错=.=

保存
下一次就直接在命令行输入make,即可生成build文件
实践路径:vcdir/makefile

=============================================

4.5 mkv210_image.c

LED闪烁的文件有很多是不需要的,有好多文件都是编译之后生成的
脚本环境下用dd命令将210.bin写入到sd卡上:(writesd文件)

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

真正的代码文件只有mkv210_image.c和汇编程序start.s
裸机程序中的makefile(实际上真正的项目的makefile都是这样的)是把程序的编译和链接两过程分开的
我们平时gcc a.c -o build是一步完成了(当然内部还是两步)
makefile中:

%.o : %.s
arm-linux-gcc -o $@ $< -c          //编译的过程,$@是 .o,$<是 .s。  -c只编译不链接
%.o : %.c
arm-linux-gcc -o $@ $< -c          //编译的过程  $@ $<是makefile的自动变量

makefile看到.c .o的文件
经过链接器(ld)生成的.elf的文件其实就可以执行了
我们需要的是可烧写的文件,即镜像,也就是image。镜像的内容和elf基本一致,只是去掉了一些无用的东西

led.bin: start.o 

    arm-linux-ld -Ttext 0x0 -o led.elf $^
    //链接生成elf

    arm-linux-objcopy -O binary led.elf led.bin
    //以elf为原材料来制作镜像

    arm-linux-objdump -D led.elf > led_elf.dis
    //反汇编,把elf给反过来
    //得到汇编程序,逆向破解。

    gcc mkv210_image.c -o mkx210
    //这句用gcc是因为不在开发板执行  

    ./mkx210 led.bin 210.bin
    //执行mkx210由led.bin 得到210.bin
    //210.bin是sd卡启动时的裸机镜像,是加工之后的led.bin。

%.o : %.s
    arm-linux-gcc -o $@ $< -c

%.o : %.c

    arm-linux-gcc -o $@ $< -c 

clean:  
    rm *.o *.elf *.bin *.dis mkx210 -f

mkv210_image.c的意义:加工的内容就是BL1的头。参照iROM手册,校验的过程在usb/uart启动时不需要16位的头(d002 0010)。而在SD卡启动时是需要校验头的(d002 0000),这个程序生成的210.bin就是加工成sd下启动具有校验头的bin文件
这个头的作用就是防止SD卡读错的,比如有干扰或者年代久远消磁了,那么读出来的校验头很可能吧1读成0,0读成1了,这样就不让SD启动了。一旦出现错误,有可能带来不可挽回的后果。比如飞机控制。

==============================================================

4.8 寄存器和数据手册

stm32和210引脚是GPIO的,51不是GPIO

GPA0: 8 in/out port - 2xUART with flow control //GPA0是端口号,后面是引脚号
D22:GPJ0_3 //LED 1
D23:GPJ0_4 //LED 2

先决条件:
1、硬件接法和引脚: GPJ0_3 GPJ0_4 高电平灭,低电平亮
2、GPJ0CON Address = 0xE020_0240
GPJ0DAT Address = 0xE020_0244

Port Group GPJ0 Control Register (GPJ0CON, R/W, Address = 0xE020_0240)
GPJ0CON:设置工作模式
GPJ0DAT:只有bit0-bit7,共8个位。bit3/4/5分别对应着led三个灯的电平
写完代码之后,要记得加一句

flag:
    b flag

防止CPU跑飞。我们的 cpu是在时间周期内不停执行指令的,如果未定义就会死机。

=============================================================

/*
 *FILE:   LED  start.s
 *AUTHOR:   KimonoYan
 *1st NoOS
 */
#define GPJ0CON 0xE0200240
#define GPJ0DAT  0xE0200244
.global _start              //把_start链接属性改为外部,这样其他文件就可以看见,少一个ld警报

_start:
                            //把0x11111111写入GPJ0CON
    ldr r0, =GPJ0CON        //GPJ0CON,ldr伪指令
    ldr r1, =0x00111000
    str r1, [r0]

                            //2nd:0x0写入GPJ0DAT,所有灯全为低电平亮
    ldr r0, =0x28
    ldr r1, =GPJ0DAT
    str r0,[r1]

    b .

使用位运算实现功能(GPJ0DAT: 0010 1000)
1<<3 1000
1<<5 100000

ldr r0 , = ((1<<3) | (1<<5)) //ldr r0 , = ((1<<3) | (0<<4)| (1<<5)) 灭;亮;灭

===============================================================

4.9 LED闪烁效果的实现:

//第一步:配置GPJ0CON

#define GPJ0CON 0xE0200240
#define GPJ0DAT  0xE0200244
.global _start              //把_start链接属性改为外部,这样其他文件就可以看见,少一个ld警报

_start:
                        //把0x11111111写入GPJ0CON
    ldr r0, =GPJ0CON        //GPJ0CON,ldr伪指令
    ldr r1, =0x00111000
    str r1,[r0]

//第二步:全亮
    ldr r0, =0x0
    ldr r1, =GPJ0DAT
    str r0,[r1]
    b .
//第三步:延时函数
    bl delay            //调用完之后的返回地址,填充到lr,这样才能跳转回来

//第四步:全灭
    ldr r0, =0xff
    ldr r1, =GPJ0DAT
    str r0,[r1]
    b .

delay:
    ldr r2, =0x100000
    ldr r3, =0  
delay_loop:
    sub r2, r2, #1
    cmp r2,r3
    bne delay_loop
    mov pc ,lr          //程序调用结束返回

【汇编–编译–链接的过程:USB调试】
1、写汇编start.s如上。 关键字:标号,循环,寄存器
2、使用工程管理。 直接make编译得到led.bin和210.bin,USB使用led.bin
3、打开DNW,插上USB 烧我们所写的led.bin

反汇编工具:objdump
反汇编是通过.elf文件生成,后缀为.dis
汇编:assembly 反汇编:disassembly
ARM汇编中,使用地址池方式来实现非法立即数。例如 ldr r0 , =0x10000 是一个伪指令,实际是用其他的汇编语句实现存储非法立即数的功能。

指令地址:下载烧录执行的bin文件,其实是一条一条的指令机器码,每个机器码带有一个指令地址
这个地址是链接器(ld)指定的。makefile中的“-Ttext 0x0”就实现了指令地址起始从0x0开始,如果改0x0起始地址就会变化
我们写的链接脚本有疑问,可以先看反汇编文件来分析这个链接脚本是否正确,这部分出现在第五部分,relocate

吐个槽这里我想到了我用的3.14的设备树反编译工具,好像是叫dtc来着。
反出来的代码真的不好用=.=
反汇编也是这样,尽可能的恢复了最精简的语言,才不会考虑你的代码注释什么的

===================================

4.10 杂项
关于b和bl
BL是arm汇编中用来调用子程序的指令,它把BL后面一条指令的地址放到R14寄存器里,R15寄存器(PC当前指针地址)就设置成要跳往的地址。这样在这个子程序返回时,再mov PC, R14就可以返回到BL后面的地址了。
B不一样,B是直接 mov PC, 目标地址;而不保存其后的地址到R14。

================================================

5.1看门狗

看门狗定时器。想象成一个场景:家门口有一只狗,这只狗定时会饿(2小时)。狗饿了就会咬人。人进进出出要保证安全就要提前喂狗。也就是说,要在上次喂过2小时内喂狗;如果超过2小时就会被咬死。现实中设备经常因为一些外部因素跑飞或死机
比如极端炎热和寒冷,工厂环境下嘈杂等。我们希望设备自动复位而不是人工干预(不能在珠峰顶上人工复位),也就是无人值守。不让设备跑飞几乎是无法完成的,自动复位就是最好的方法,这里就用到了看门狗、
看门狗在系统正常工作会自动喂狗,由于喂狗的代码和系统是在一起的,系统跑飞了就不能喂狗了。系统随之强制复位

学习的内容是关闭看门狗。关看门狗的外设 SFR
Watchdog Timer Control Register (WTCON, R/W, Address = 0xE270_0000)
Watchdog timer
bit [5]
Enables or disables Watchdog timer bit. 0 = Disables 1 = Enables
汇编关闭看门狗的代码 :

_start:
    //disable WDT

ldr r0 , =0xe2700000    //地址加载进来
ldr r1 , =0x0       //把看门狗bit5关闭,但会误伤看门狗的其他设置
str r1, [r0]    

================================================

5.2 汇编写启动代码之 设置栈和调用C语言

我们迟早要过渡到c语言,不能一直使用汇编的
“c语言运行时“:即常说的 “Runtime”。c语言是需要汇编完成环境才能运行的,那么我们需要自己用汇编提供c语言运行时环境。
主要,是需要栈。c语言的局部变量都是存在栈里面的
如果汇编部分没有为c预先设置合理合法的栈地址,那么c中定义的局部变量就会落空,程序也就随之死掉了
我们平时在编写单片机程序或者是gcc的应用程序我们都没去管栈,原因是单片机在初始化时,硬件提供了默认可用的栈
在应用程序中,我们编写的c程序并不是全部。编译器链接的时候帮我们自动添加一个头,所谓的“头”就是引导c程序由汇编实现的,支持c语言的执行
嵌入式底层的问题也就要难,比编写应用程序的难度,不知道高到哪里去了

各个模式下不同sp的原因:各个模式使用不同的栈,避免一个栈出问题导致整个操作系统崩溃
我们设置栈,先找到自己的模式,设置自己模式下的栈到合理合法的位置即可
系统在复位和默认是进入SVC模式的,那么我们要设置的 是SVC模式下的栈

先把模式设置为SVC,再操作sp(r13)。由于复位后就是svc模式了,所以直接设置sp即可

栈必须是当前一段可用的内存,必须有被初始化过可以访问的内存,而且这个内存只会被我们用作栈,不会被其他程序征用
CPU复位后,DRAM尚未初始化,我们只能用内部的SRAM。

iROM的操作手册上,看到SVC Stack的相关内容,有1.5k,地址应设置为0xd003 7d80
在ARM中,ATPCS要求使用【满减栈】

设置SVC栈:

    ldr sp , =0xd0037d80

这句只后都可以用c语言实现了hhhh!

======================================================

5.3 使用c的其他细则

1、Makefile 加上start.o 和 led.o
//在模板的Makefile添加一个依赖!很重要!

make…

2、编译报错,在makefile中 加上 -nostdlib
3、使用c语言编写延时函数,函数要加volatile
4、修改寄存器位之定式:

    unsigned int *p = (unsigned int *)0xE0200240;
    *p = 0x1111 1111;

我们之所以可以用c语言指针的 方式去操作寄存器,知道寄存器地址就可以写
是用了一个”IO和内存统一编址”的原理

//2015-9-18 学习使用c语言代替汇编语言

=========================================================

5.4 iCache

cache的作用是:
CPU的运行速度是远大于DDR的,cache(高速缓存)在CPU或者寄存器 和 DDR之间做缓冲作用
一句指令被读取,同时cache会 保存附近的几条代码,当读到这些代码时,cache即会将这些代码给CPU

mrc
mcr可以

系统中供应链:寄存器 + Cache + DDR + 硬盘/flash , 共4阶组成
查IROMappli可以得知,x210有64KB的一级缓存和512KB的二级缓存
一级缓存:
32KB icache 缓存指令
32KB dcache 缓存数据
icache的一切动作都是自动的,不需要人为干预,要做的就是on/off icache而已=v=

cache在哪里呢?
在cp15里,是一个协处理器寄存器
cp15的 bit[12] 0:关闭cache 1:打开cache
关闭cache后的led灯闪烁变慢了2-3倍

=========================================================–

5.5 重定位 stage1

重定位:实际就是在运行地址处执行一段位置无关码,再从运行地址处镜像拷贝整个代码到链接地址处,然后长跳转到链接地址处 执行位置有关码(led_blink)
位置有关/ 无关编码(PIC)
两个地址;
(链接地址):位置有关编码必须给编译器和链接器标明这个地址。
(运行地址):给程序运行指明的地址称为运行地址。
大部分的代码都是位置有关编码
对于位置有关代码来说,最终执行的运行地址和链接时给定的链接地址必须相同(或者映射相同),否则一定出错

三星推荐的启动方式:bootloader必须小于96kb,大于16kb。BL0会加载bootloader的前16KB(BL1)到SRAM中运行,BL1运行时会加载BL2(也就是BL1-16KB)到SRAM。BL2运行时会初始化DDR并且将OS搬运到DDR中去执行OS
uboot实际使用的方式:整个大小是随意的不作要求。假设是200kb,那么执行过程是这样的:先开机上电后BL0运行,BL0会加载bootloader的前16KB(BL1)到SRAM中运行,之后将整个uboot搬运到DDR,然后用一句长跳转指令从SRAM中跳转到DDR中继续执行uboot,uboot启动后在uboot命令行中去启动OS。
相比之下,uboot这种方式更为灵活。但是很多工作宁愿自己去写bootloader。因为uboot很多功能是用不到的

为什么重定位:
原因: 1、有些时候,链接地址和运行地址必须不相同
2、不能全部使用位置无关码。
此时只有重定位才能解决问题。比如uboot。其他的方法:分散加载,把uboot分成两部分(BL1和整个uboot,两部分分别指定不同的链接地址,BL1加载到SRAM,uboot加载到DDR)
分散加载:手工进行重定位
重定位:代码进行重定位

==============================================================

5.5 重定位2和连接脚本 【重定位 = 代码搬移】

运行时地址由运行时决定,编译链接时无法确定
链接地址在编译链接中,通过makefile中 -Ttext xxx或者链接脚本决定

linux中,gcc hello.c -o a 没有指明链接地址,但应用程序都是链接在0地址的。在操作系统的一个进程中,这个应用程序独享4G的虚拟地址空间,应用程序都可以链接到0地址。
也就是说,每个应用程序都以为整个4G内存中只有他自己,这就是操作系统给我们带来的便利。

x210裸机中。运行地址由我们下载时确定,下载时下载到0xd0020010,所以从这里开始运行。这个下载地址也不是我们所以定的,而是IROM中BL0加载BL1时事先指定好的地址,这是由CPU的设计决定的。【下载到别的地方,导致约定的地方没来,就会宕机】

源码到可执行程序:预编译、 编译、 链接、 (可选步骤)strip
.c .s .o 按顺序打包 把可执行程序中的符号信息拿掉以节省空间

程序段的概念: 代码段 数据段 bss段 自定义段
段就是程序的一部分,我们每个段命名,在链接时就可以用这些名字指示段。从而让段在链接时站在合适的位置 段名分两种:先天的和程序员自定义的段名

先天指定的

代码段 又叫.text段和文本段。其实就是源码中的函数,编译后生成的东西就是代码段
数据段 .data c语言中有初始化非0的全局变量 int a_global = 10; 以及static局部变量
bss段 .bss,又叫ZI(zero initial),对于c语言中初始化为0的全局变量 int b; int b = 0;

后天性段名:

段的属性、特征、姓名由程序员自己定义

注意:局部变量不能算程序的数据,只能算函数的数据
有些特殊数据被放在代码段: (1)char a[] = “linux”; 中的”linux”字符串,相当于常量

链接脚本:本身是一个规则性文件,他是程序员用来指挥链接器工作的。链接器会参考链接脚本,使用其中的规则处理.o的那些段,链接生成elf
关键内容:段名+地址
SECTION {}
.在链接脚本中代表当前地址 . = 0xd0024000; //给当前地址赋值0xd00240000 , 记得这一句加“;”

    .text : {               //段名,冒号,大括号
        start.o
        * (.text)
    }
注意:如果有多个.o,那么顺序是按照书写的顺序严格排列,* (.text)代表剩下的可以随意排列

链接脚本中有一些符号,比如
bss_start = . ;
那么这些符号在汇编语言中就是可用的
linux中有很多自定义的

=============================================================

5.8 重定义实战:

任务:将SRAM中将0xd0020010重定位到d0024000运行!
本来代码是运行在0010的,但是希望在4000运行,这时候就需要重定位了。目前的 实验不是必须 的,但到 了uboot就是必须的了0.0

第一点:通过链接脚本将代码链接到0xd0024000
(-Ttext 只能实现比较简单的功能,链接脚本才是实际的应用)
. = 0xd0024000 //主要是这一句
Makefile中 arm-linux-ld -Tlink.lds -o led.elf $^
第二点:dnw下载到0xd0020010
/////以上两点保证了程序下载运行在0010,却链接在4000。我们就需要重定位,在第一句位置有关码读取之前,把运行代码从0xd0020010搬运到0xd0024000去,这样才能保证顺利 的执行;
本次实验中,第一句位置有关码是 ldr pc, =led_blink
如果在这句程序执行之前没有完成重定位,那么程序就死了

第三点:通过代码前段的少量位置无关码将整个代码搬移到0xd0024000
编写相关汇编代码
【此时会有两段相同的代码的镜像】
第四点:使用一个长跳转跳转到0xd0024000处的代码继续执行,重定位完成
长跳转:跳转指令是通过给PC(r15)赋新值完成跳转执行。长跳转就是跳转的范围比较宽的跳转,比如前后差了好几兆

横向对比:
短跳转: bl led_blink 执行0xd0020010开头的一段
长跳转: ldr pc, =led _blink 执行0xd0024000开头的一段
注意:当链接地址和运行地址相同时,长短跳转是相同的

===========================================================

//第四步:重定位

adr与ldr伪指令的区别:ldr和adr都是伪指令,ldr是长加载,adr是短加载。
adr在加载符号地址时是运行地址,而ldr加载的是链接地址

    adr r0, _start  //加载_start的运行地址(程序运行时pc处于运行地址0xd0020010)
    ldr r1 , = _start   //加载_start的链接地址 ldr r1,[pc,#72] ldr pc向下偏移72个字节给r1,这里面存的是4000
    ldr r2 ,=bss_start  ///r2 = bss段的起始地址
    cmp r0, r1  //运行地址 ? 链接地址
    beq clean_bss   //如果运行地址 = 链接地址,则不需要重定位
            //若不相等就需要重定位

重定位的代码拷贝函数就是汇编代码中的copy_loop函数,使用循环结构来逐句复制代码到链接地址。

复制的源地址是0xd0020010 目的是0xd0024000
复制程序的长度是:

copy_loop:
    ldr r3 ,[r0],#4 
    str r3 ,[r1],#4
            //int *p1 = dst;   int*p2 = src;
            //*p1++ = *p2++;    //等同于上述汇编代码
    cmp r1, r2  //链接地址 ? bss段的开始    
    bne copy_loop

注意:bss_start是重定位的结束地址,也就是说重定位只需要重定位代码段和数据段即可
//清除bss:
在0xd0020010的bss是会自动清零的
而在我们自己重定位的代码是不会的,清除bss段是为了满足c语言的运行时要求(要求显示初始化为0的全局变量值为0)
C语言编译器和链接器帮我们做了这件事,通过自动添加一个main函数之前的头,负责帮我们清除bss

clean_bss:
    ldr r0 ,=bss_start
    ldr r1 ,=bss_end        //ldr pc叫长跳转,ldr r0等寄存器叫长加载
    cmp r0, r1
    beq run_on_dram
    mov r2, #0

clear_loop:
    str r2, [r0] ,#4        //*(r0++) = r2; 
    cmp r0, r1
    bne clear_loop

run_on_dram:
    //长跳转到led_blink
    ldr pc ,= led_blink
    //短跳转:  bl led_blink

    b .

===============================================================

5.10 SDRAM引入

重定位真正用的地方是SDRAM
SDRAM:syncronized Dynamic RAM 同步动态随机存储器,也就是平时说的 “内存”。DDR就是DDR SDRAM,升级版
SDRAM的特性:相对于SRAM,属于动态内存,需要一段代码进行初始化使用
类似于NorFlash和Nandflash(硬盘):Nor可以直接用,Nand不行
现在做SDRAM的厂商不是很多,大部分都是台湾和韩国产的,二线厂家向一线厂家看齐。三星、LG、kingston。比较标准化
在我们的板子上使用的不是K4T1G164QQ,但可以看这个原理图,两者完全兼容
x210最大支持的DRAM是两个端口
DRAM0 20000000 - 3FFFFFFF 512MB
DRAM1 40000000 - 7FFFFFFF 1024MB
我们的x210开发板上使用了2x256MB的DRAM
合法地址是0x20000000 - 2FFFFFFF
0x40000000 - 4FFFFFFF //中间不连续,用的话是自寻死路

原理图中每个DDR端口都由3类总线构成:地址总线(XmnADDRA0 - 13)+ 控制总线+ 数据总线(Xmn_DATA0 -31)
2个DDR使用了并联的方法。参考底板原理图,两个DDR的数据总线是16+16,共同凑成32位内存
26:52
板子上有4个芯片,实际上可以认为是2个
210的DDR端口信号上有BA0-BA2 用来选择bank。每个bank有128Mb,通过列row地址+行col 地址来综合寻址
列14位 行10位,一共能寻址的范围是2的14次方+2的10次方 = 2的24次方 也就是16MB = 128Mbit

=============================================================

5.12 SDRAM下的重定位

修改relocate内容:
1、链接脚本LINK.s
. = 0x20000000 //重定位到SDRAM
2、start.s
在重定位之前,加入初始化DDR的代码
bl sdram_asm_init
3、sdram_asm_init函数
sdram_asm_init在sdram_init.s中实现

    #include "s5pv210.h"
    ...
    .global sdram_asm_init
    sdram_asm_init:
        ...
        mov pc, lr  

注意:汇编实现的函数在返回时需要明确使用返回指令

DDR的初始化和SoC有关,和开发板使用的DDR芯片有关,也和设计时的DDR连接方式有关
初始化DDR需要27个步骤,共300余代码
之前分析过x210的内存分为DRAM0 DRAM1两部分。SDRAM的初始化也需要两部分

SoC中有DDR控制器
这部分代码不需要自己写,九鼎官方uboot已经提供了,但我们自己可以改写。给我们的代码是完整的,但是没有详细描述,甚至有些寄存器根本没有描述。这部分代码多半是来自于三星官方,只有三星知道。

DRAM芯片工作的时候需要设置一定的驱动信号,这个驱动信号需要一定的电平水平才能抗干扰,应设置为2x
….
PHYCONTROL 0 this value should be 0x10?

DMC0_MEMCONTROL 对应值: 0x0020 2400
设置为 burst length = 4 ; 1chip …
一次读4个字节,使用一个逻辑内存(注意是逻辑,不是物理。老朱在这里讲的自己也不知道怎么回事,给我们的INIT.s有问题)

DMC0_MEMFIG_0 对应值: 0x20F01323
DRAM0通道的memory chip参数设置寄存器
20:0x2000 0000起始地址(这个起始地址已经硬件写 好了居然还让我们写!DMC1是0x40)
F0:F0是掩码 前4位是0:对应256M
1323:地址映射法则,不重要

DMC0_MEMFIG_1
DRAM1通道的memory chip参数设置寄存器

=============================================================

六丶时钟
6.1
时钟是同步工作系统的同步节拍
PLL锁相环:他的功能就是倍频,产生一个很高的 频率
大部分的SoC时钟都是外部晶振+内部时钟发生器+内部PLL产生高频器分频
芯片的 外部电路不适合使用高频率,所以内部使用

6.2 s5pv210的时钟系统的具体特征
数据手册:um system clockconctrler

x210时钟系统分为三部分:
时钟域:
MSYS域 DSYS域 和 PSYS域 通过桥梁链接这三部分,但三大块基本是独立的
这么分是因为时钟太复杂了,时钟速率也各有差异,不得不根据速率分为三大块
高频 MSYS: 包括CPU,cortex内核,DRAM控制器(DMC0,DMC1), 此外还有IRAM,IROM…
master
中频 DSYS: 视频编解码,jpeg编解码……
210设计的本质功能是想做平板电脑的,所以必须要视频的硬件解码
digital
低频 PSYS: 和内部外设有关系,包括串口,SD卡,I2C、AC97(连接声卡)、USB、RTC等

时钟来源:
晶振+时钟分频器
外部有时钟发生器,也就是晶振,有多个(最多4个,一般只会接一个,有USB/HDMI等)
system timer 有三信号来源,三选其一

时钟图的一个重要之处是MUX开关,通过寄存器设置0或1,从而选择时钟来源是哪一个,这个以后详细讲

APLL    MPLL    VPLL    EPLL    四份锁相环电路,各自为各自工作。
信号来源要看图(um_)

S5PV210有4个晶振接口,产生原始时钟。经过一系列筛选后进PLL生成高频时钟,再经过分频即到达各个模块。
模块上可能有进一步的分频器再次分频,也可能没有。串口就有。

【APLL给我们的MSYS域使用,这句比较重要】
【MPLL / EPLL : 给Dsys  Psys】
【VPLL给videos使用的】
根据这个,将来就知道谁是用谁的。

210时钟详细解读在后面

=======================================

6.3 x210 时钟域

MSYS:
    ARMCLK      主频
    HCLK_MSYS   MSYS的高频时钟
    PCLK_MSYS   MSYS的低频时钟
    HCLK_IMEM   给irom和IRAM使用

DSYS
    HCLK_DSYS
    PCLK_DSYS

PSYS
    HCLK_PSYS
    PCLK_PSYS
    ... 
X210的外设都是连接在AMBA总线上的,这个总线分为一个高频AHB和一个低频APB
然后, HCLK_XXX 是AHB 的工作频率
    PCLK_XXX是APB 的工作频率
210的外设是挂在总线上的,也就是说这个外设的时钟来自其所在的总线。

典型时钟频率
x210刚刚上电时,初始的主频是24MHz直接给ARMCLK,IROM初始化之后才给OS默认的频率
当然,这个推荐的频率也可以人为的修改,超频或者降频,要看你硬件能不能支持起来。
当开启了APLL的时候,可以通过后面的MUX开关打到1,从而获取1Ghz的FOUT_APLL

?freq(ARMCLK)       = 1000 MHz
?freq(HC    LK_MSYS)    = 200 MHz
?freq(HC    LK_IMEM)    = 100 MHz
?freq(PC    LK_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倍频===(高频信号)===[DIV分频器]===总线时钟

MUX:多选一开关,对应着寄存器中的某几个位 clock source x寄存器
DIV:分频器,分频系数n就是除以n,对应着寄存器中的某几个位 clock divide x寄存器

===============================================================

6.5 时钟设置的关键寄存器

(重要) xPLL_CON PLL控制位,有的时候1个位,有的时候是2个
使能,锁定状态(只读),倍频参数:M/P/S DIV(通过公式计算,APLL默认C8 3 1,1Ghz)
(重要) CLK_DIV 分频器
(重要) CLK_SRCn (n:0-6) 设置时钟源
就是设置MUX的

xPLL_LOCK 控制PLL的锁定周期
PLL锁相环是通过一个循环实现,锁定周期就是锁住他让他输出稳定值的,一般为默认
CLK_SRC_MASK 是时钟源的开关
默认的都是打开的,如果uboot的时候关掉,那么后面一定不会自己打开,要注意。
这个是针对时钟来源的开关
CLK_GATE 非常类似srcmask,对时钟进行开关控制。0:mask 1:pass
这个是针对输出时钟的开关

CLK_DIV_STATn
CLK_MUX_STATn
状态位寄存器,用来查看DIV和MUX的状态是否已经完成,或者是在进行中

==================================================================–

// 3 设置分频

// 清bit[0~31]  
ldr     r1, [r0, #CLK_DIV0_OFFSET]                      
ldr     r2, =CLK_DIV0_MASK                      
bic r1, r1, r2
ldr r2, =0x14131440                         
orr r1, r1, r2  
str r1, [r0, #CLK_DIV0_OFFSET]

CLK_DIV0设置为0x14131440结果:
PCLK_PSYS = HCLK_PSYS / 2
HCLK_PSYS = MOUT_PSYS /5

PCLK_DSYS = HCLK_DSYS / 2
HCLK_DSYS = MOUT_DSYS/4

PCLK_MSYS = HCLK_MSYS /2 100
HCLK_MSYS = ARMCLK /5 200

SCLKA2M = SCLKAPLL / 5
ARMCLK = MOUT_MSYS / 1 1000

ARMCLK不分频:1Ghz = MOUT_PSYS

=============================================================

第7章 串口,emmmmmm记录在裸机笔记3中。

你可能感兴趣的:(纯真的自学笔记喵)