S3C2440 开发板实战(2):start.S初认识 + SDRAM配置 + 重定位
2022-07-18 来源:csdn
1、看门狗部分
废话不多说,直接开始配置,首先就是要关闭暂时不使用的看门狗,找到看门狗的寄存器:WTCON,将其第0位置0,即禁用看门狗,即:
# define pWTCON 0x53000000 //WTCON地址
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0] //关闭看门狗
2、时钟部分
在S3C2440中有三种时钟频率,分别为FCLK, HCLK, PCLK,分别控制不同种类的外设,在时钟树中可以进行查找,这里不做过多赘述,在以后的博客中有应用。
从芯片手册中查找FCLK, HCLK, PCLK 的最高频率,在范围内我们选择设置FCLK = 400MHZ, HCLK = 100MHZ, PCLK = 50MHZ (TF : TH : TP = 1 : 4 : 8)为例进行设置初始化时钟,看芯片手册的时钟树如下图所示:
OSC可以理解为我们的晶振频率,从开发板的原理图上可以看出为12MHZ。
时钟信号通过OM[3 : 2]( 外接引脚 GND )进行时钟源选择,即设置OM[3, 2] = [0, 0],此时刚上电时FCLK频率等于晶振频率,然后进入lock time,此时,CPU暂停工作,在lock time (该段时间是为稳定输出新的FCLK)过后CPU继续工作,CPU时钟的主频率为设置的FCLK。
为保证CPU能够在启动后一定能发送设置的FCLK信号,所以将FCLK设置为最大值:
# define LOCKTIME 0x4C000000
ldr r0, =LOCKTIME
ldr r1, =0xffffffff
str r1, [r0]
先不看控制USB部分(UPLL)直接看MPLL部分。时钟树中时间信号通过锁相环(PLL)生成400MHZ的FCLK。其中关于MPLL寄存器设置方法如下:
然后通过查找数值表(也可以手算,在手册中有公式),由Input Frequency = 12MHZ,Output Frequency = 400MHZ得到MDIV, PDIV, SDIV的参数值,并写入寄存器中:
# define MPLLCON 0x4C000004 //MPLLCON地址
ldr r0, =MPLLCON
ldr r1, = ((92 << 12) | (1 << 4) | (1 << 0)) // MDIV = 92 PDIV =1 SDIV = 1
str r1, [r0] //写入寄存器
时钟树通过参数HDIVN, PDIVN对FCLK进行分频操作得到可操作进行外设操作的时钟HCLK, PCLK,设置分频系数的寄存器为CLKDIVN:
# define CLKDIVN 0x4C000014
ldr r0, =CLKDIVN
ldr r1, =((10 << 1) | (1 << 0)) // HCLK = FCLK / 4
// PCLK = HCLK / 2
最后一步由于设置的HDIVN寄存器不等于0,所以需要设置CPU处于异步状态,这里涉及到协处理器的命令(我也不懂哈哈哈),就按照手册中的来编(chao)写就行了。
3、代码重定位
这里先要讲一讲,由于nor flash在运行程序时,是可读不可写的,所以如果程序中有变量储存在 .bin文件中时,该变量不可被改变,即使在程序中对其进行修改。所以我们需要对代码进行重定位到SDRAM中,然后才能对其全局变量进行修改。所以首先应该讲下关于SDRAM的初始化配置:
3.1. SDRAM
由于在start,S程序中只要对SDRAM进行初始化配置,这样就能够对代码进行重定位,所以本章节博客只对SDRANM的初始化程序进行编写以及一些基本操作。
S3C2440中可以支持很多的存储单元,是通过地址对其进行区分的:
内存控制器通过识别不同的地址发出不同的片选信号(0信号低电平有效)。现针对SDRAM分析,2440外设置的是64M的SDRAM,需要对其进行设置寄存器,其SDRAM可以理解为3维的存储空间,bank * row * col,所以大体过程是由CUP发出地址,经过内存控制器转换成row, col, bank信号(为减少pin)
------------------------------------------------------------------------------------------------------------------------------------------------------
既然使用SDRAM就要和CPU说清楚这是个啥类型SDRAM,毕竟人家也是按需办事,所以配置寄存器BWSCON!
ST6:首先来看看芯片手册这么一句话:“nBE[3:0] is the 'AND' signal nWBE[3:0] and nOE”,即nBE是nWBE和nOE的“与”信号,所以nBE是字节选通信号(读+写),nWBE是读字节选通信号。
由于在进行SDRAM操作的时候只需要在写入的时候需要选择比如说32位中的前4位,输出时候把全部的输出(这是韦老师讲的,这块我也不了解,以后填坑把),所以置0.
WS6:wait的使用时因为有些质量比较差的储存芯片的反映速度没那么快,你叫他半天不理你和痴呆似的,需要等待他就秒,就是这个道理。开发板上的SDRAM比较好所以不等待。置0
SW6:由JZ2440是由两片SDRAM组成起来的32位SDRAM,所以应该选择10
一般会把CS7设置成CS6(也是SDRAM)但是没有使用它。
所以设置BWSCON = 0x22000000;
------------------------------------------------------------------------------------------------------------------------------------------------------
接着设置寄存器BANKCONn,和BWSCON一样,同时设置CS6和CS7。
可以看出[4 : 14]位都不需要我们设置!而且MT很清楚就知道应该置“11”,接着就是[0 : 3]位的配置。
Trcd的值是芯片的参数,根据HY57V561620F(L)T(P) 芯片手册:
为了和时钟频率对应,取20ns。即2 CLOCKs,将[3 : 2]置'00'
继续查看芯片手册:
明显Column address number = 9,所以设置[1 : 0]为“01”。
所以设置寄存器
BANKCON6 = 0X18001;
BANKCON7 = 0X18001;
------------------------------------------------------------------------------------------------------------------------------------------------------
在2440芯片手册往下拉继续配置寄存器(我也觉得麻烦,没办法继续走)。
接着就是REFRESH寄存器,这个寄存器作用就是使SDRAM自动刷新,这是一个内部的自动操作。由于SDRAM要不断进行刷新(Refresh)才能保留住数据(储存体中电容的有效保存期有限),因此它是SDRAM最重要的操作。这么重要的参数,咋们肯定去SDRAM芯片手册中进行查找啦(保有),首先看看寄存器是怎么配置的:
REFEN:使能没得跑置“1”。
TREFMD:选择自刷新(SR)和自动刷新(AR)。对于AR,SDRAM内部有一个行地址生成器(也称刷新计数器)用来自动的依次生成行地址。由于刷新是针对一行中的所有存储体进行,所以无需列寻址。对于SR,在发出AR命令时,将CKE置于无效状态,就进入了SR模式。此时不再依靠系统时钟工作,而是根据内部的时钟进行刷新操作。在SR期间除了CKE之外的所有外部信号都是无效的(无需外部提供刷新指令),只有重新使CKE有效才能退出自刷新模式并进入正常操作状态。
所以我们选择AR模式对其进行数据的刷新。置“0”默认值。
Trp: 废话不多说直接查芯片手册
老道理,取整 2倍CLOCK。[21 : 20]为“00”
Tsrc:这个就比较特殊了。芯片手册没有嘿嘿。再看看描述中的式子。Trc = Tsrc + Trp.那这么说找到Trc就是我们的目标!那又回到最初的起点,开查芯片手册!就在Trp的上边。取整取70ns,大一点没坏处!Tsrc = 70 - 20 = 5*CLOCK ---> [19 : 18] 置“01”。
Refresh Counter :这个就是最重要的刷新频率了。这参数就放在SDRAM芯片手册的前面几面。
果然找到Refresh period = 64 / 8192 ms,带入公式中得到
由于时钟周期为7.8us,且时钟周期为HCLK = 1000MHZ,Refresh = 1269;
综上所述,REFRESH = 0x8404f5。
------------------------------------------------------------------------------------------------------------------------------------------------------
第四个寄存器BANKSIZE,上图!(半夜两点的我已经不知疲倦)
BK76MAP:这个简单,开发板配套的SDRAM容量为256Mb,注意这里是小b,所以容量大小为256/8 = 32MB,由于外设搭载两片SDRAM,所以容量为64MB。置[2 : 0] ='001'
SCKE_EN:断电模式启用 (以后填坑,先开先)
SCLK_EN:推荐值(以后填坑,先开先)
BURST_EN : 启用突发操作。(以后填坑,先开先)
所以BANKSIZE = 0x000000b1
-----------------------------------------------------------------------------------------------------------------------------------------------------------
最后一个寄存器,MRSRBn
这个寄存器特点:简单!有fixed值就选fixed值!
唯一需要选择的是CL值,一看诶。芯片手册上边查就行了,这个值的意思是由于在读SDRAM时需要发出:bank row col地址,所以需要等一会儿才有数据发回来。所以在芯片手册中可以查到CL=2或者3,这个值设置后回发送至SDRAM中的MR寄存器,以后在2或者3clock时刻返回数据。所以我们设置为2clock 即 =》“010”
所以MRSRB6 = 0x20; MRSRB7 = 0x20;
-----------------------------------------------------------------------------------------------------------------------------------------------------------
综上所述,对于SDRAM的初始化函数程序如下所示
#include 's3c2440_soc.h'
void sdram_init(void)
{
BWSCON = 0x22000000;
BANKCON6 = 0x18001;
BANKCON7 = 0x18001;
REFRESH = 0x8404f5;
BANKSIZE = 0xb1;
MRSRB6 = 0x20;
MRSRB7 = 0x20;
}
3.2. 判断设置是nor flash启动还是nand flash启动
这一段代码主要是基于NOR flash 可读不可写,然而NAND flash是可读可写的特性,所以可以通过对某一地址进行写数据对其进行判断,但如果是NAND falsh启动的话则会破坏内存,所以还需要保护数据
mov r1, #0
ldr r0, [r1] // 保存数据
str r1, [r1] // 写入数据
ldr r2, [r1] // 读出数据
cmp r1, r2 // 如果相等 Z = 1
ldr sp, =0x40000000 + 4096 //NOR启动
moceq sp, #4096 // NAND启动
streq r0, [r1] //恢复数据
但是对于NOR flash启动的u-boot,他就没有管那么多了。反正他要使用NOR启动,在Start.S文件中使用以下代码,区别就是破坏了NAND flash部分代码
ldr sp, =4092
ldr r0, =0x12345678
str r0, [sp]
ldr r1, [sp]
cmp r0, r1
ldrne sp, =0x40000000+4096
bl clock_init
3.3. 代码重定位
首先用XXH查看dis文件(返回编码文件)。(该文件的程序里面包括了初始化的全局变量和未初始化的全局变量。
用VIM打开dis文件,/搜索关键词:Disassembly of section 查找到程序数据类型包括:
.text、.rodata、.data、,bss、.comment等等
其中看源文件的文件大小可以知道 .bss段是不在bin文件的内容中的这个段包括了未初始化的全局变量,.data段是在bin文件范围中包括了已经初始化的全局变量。这里有两种转移代码的方式:①把需要改变的部分移至SDRAM,②把全部程序移至SDRAM。我们选择第二种进行移动(链接脚本比较简洁)。所以应该把.data的部分进行拷贝。
所以这个时候就要使用链接脚本 *.lds文件,通过查阅Using ld The GNU linker有一下源代码格式:
SECTIONS {
...
secname start BLOCK(align)(NOLOAD) : AT (ldadr)
{ contents } >region:phdr =fill
...
}
start: 起始地址
runtime addr:运行时的地址
relocate addr:重定位的地址
AT(ldadr) Load Addr:加载地址 可以省略不写
LoadAddr = runtime addr 如果没有加AT,它的的加载地址就等于链接时的起始地址
以实例来进行说明:
sdram.lds
SECTIONS
{
. = 0x30000000; //设置当前地址为SDRAM首地址
. = ALIGN(4); //向四取整
.text :
{
*(.text) //Load Addr = Routime Addr
}
. = ALIGN(4); // 紧接着.text文件排放
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(.COMMON) }
_end = .;
}
/* 重定位text, rodata, data段整个程序 */
mov r1, #0
ldr r2, =_start /* 第1条指令运行时的地址 */
ldr r3, =__bss_start /* bss段的起始地址 */
cpy:
ldr r4, [r1] //r1 --> r2
str r4, [r2]
add r1, r1, #4 //循环4次 (16byte/4byte)
add r2, r2, #4
cmp r2, r3
ble cpy
/* 清除BSS段 */
ldr r1, =__bss_start
ldr r2, =_end
mov r3, #0
clean:
str r3, [r1]
add r1, r1, #4
cmp r1, r2
ble clean
注:
* ldr: 这里我们可以进行ldrb(1byte)或者使用ldr (4byte),由于sdram是16byte的,所以使用ldr函数能够极大的减少访问SDRA的次数:、
①原来:读ldrb执行16次指令,并且访问16次SDRAM;写strb执行16次,并访问16次SDRAM
②现在:读ldr执行4次指令,并且访问8次(我也很迷惑),写str执行4次,并访问4次SDRAM(每次读出四字节)
*写程序:在写程序中会发出地址加上DPM,把感兴趣的对应写入,对应的是SDRAM配置中的写使能
*.bss段:由于bss段数据都是未初始化,都是0,但是转移后的位置可能存的不是0数据,所以应该对其进行初始化置“0”
* 向四取整:由于ldr的操作是四个字节的赋值,但是我们的地址并不是四字节对齐,所以会进行向四取整
实例:
命令存放地址 3 0 0 0 0 0 0 2 ( 2 < 4)
真实存放地址 3 0 0 0 0 0 0 0
所以应对方法也是对当前地址进行向四取整即:
. = ALIGN(4);
这样命令存放的地址就变为:
实例:
命令存放地址 3 0 0 0 0 0 0 4 ( 4 >= 4)
真实存放地址 3 0 0 0 0 0 0 4
这就是start文件初认识了!