9.初识内存控制器和SDRAM

目录

1.引入内存控制器

2.不同位宽内存设备之间的连接

3.如何确定芯片的访问地址

4.分析读写NOR FLASH的读写时序

5.SDRAM初识

6.编程读/写 SDRAM

附录:源代码


1.引入内存控制器

我们RAM芯片是如何操作GOIO管脚的,是如何控制UART串口的?

答:假设我们需要控制GPIO管脚,其实就是CPU发出指令给内存控制器,内存控制器再发送指令给GPIO控制器,GPIO控制器收到指令然后进行工作,CPU指令是不能直接到达GPIO控制器的,但是CPU指令通过内存控制器可以直接到达内存设备。

这时就引入了一个内存控制器,如下简图:

问题:

可知32位的ARM芯片地址总线只有32根,所以很多内存设备是共用地址总线或者数据总线的,操作的时候总不能多个内存设备一起操作吧,那CPU是如何区分和哪个设备进行连接的呢?

解答例子:

以S3C2440的原理图为例,其CPU有地址总线和数据总线:

但是这块CPU连接了很多内存设备,比如:NOR,SDRAM,DM9000,如下:

DM9000:

注意:其中LnOE,和LnWE是控制数据传输方向的 

NOR FLASH:

注意:其中LnOE,和LnWE是控制数据传输方向的  

SDRAM:

注意:LnWE是控制数据传输方向的  


他们直接的连接可以用一张简图来表示,如下:

既然有多个内存设备连接到同一个总线,那么就需要在使用的时候把它们区分开来,是如何做的呢?

: CPU把地址发送给内存控制器,根据不同的地址来选择发送哪个片选信号的,只有当一个内存设备的片选信号被选中的时候才可以使用,否则就跟没有连接是一样的,所以,一个时刻只能有一个设备被选中。

那地址和片选信号的关系呢?在S3C2440官方芯片手册就有提供这些信息,如下:

从下图可以看出,每一个片选信号可以选择的地址空间是:128M   = 2^27 ,理论上只需要27条地址线。 

所以说,多个芯片使用同一总线是如何互不干扰的呢?

答:内存控制器根据不同的地址,发出不同的片选引脚,只有被选中的芯片才会工作,不选中的芯片不工作,因此他们之间可以通过地址间接的决定工作的设备。

所以可以说,GPIO,UART、IIC,以及SDRAM,DM9000,NOR FLASH都属于CPU的统一编址。

注意:Nand Flash 不属于CPU的统一编址

可以从原理图看出,虽然数据线来自于内存控制器,但是并没有分配数据线

但是它受控于Nand控制器:

注意:


讨论另一个问题?CPU的32位总线是否全部使用了呢?

可以看一下,每个片选信号可以使用的地址空间是:0x08000000   减去 0x00000000  转换成十进制就是,134217728 byte

也就是:134217728byte /(1024*1024) = 128M  = 2^27.那么理论上至少需要27 条地址线(A[0] A[1] ......A[26])

这时可以查看一下数据手册,发现真的只有地址线(A[0] A[1] ......A[26]) ,为什么? 从原理图中看出,地址线和数据线明明是32位的呀,在数据手册怎么就剩27位了?

答:这是因为有几位给内存控制器给扣留啦,虽然CPU发出了32位的地址,但是内存控制器只用了27位,简图入下:


2.不同位宽内存设备之间的连接

内存控制器地址线和片外内存设备的接法:

可以发现他们的接法好像都是不同的,他们的规律是什么?

这时需要去查看一下芯片手册。

8-bit ROM

16-bit ROM

32-bit ROM

规律:那么就是说外接芯片的位宽有变化,那地址线的接法也有变化。

大致如下简图所示:

那为什么是这样呢?需要了解数据内部的保存结构,简图如下:

假如现在执行指令:

mov  R0 ,#3    --把3的数值赋值给R0

LDRB R1, [R0]  --读取地址为3的位置读取1个字节的数据

那么CPU就会把地址3发出去,也就是:0x00000011  ,那么此时地址线 A0  =1, A1 =1 ,其他(A2-A27)为0.

  • 如果外界是8-bit ROM,因为A0接A0,A1连接A1,此时他收到的就是0x3
  • 如果外界是16-bit ROM,因为A1接A0,A2连接A1,此时他收到的就是0x1
  • 如果外界是32-bit ROM,因为A2接A0,A3连接A1,此时他收到的就是0x0

我们的任务时读取第3byte的数据:

当为8-bit ROM,那我收到的正好也是0x3这个数值,没有问题。

当为16-bit ROM,我收到的是0x1,这个数值,也就是十进制的1,读取地址编号为1的地址的值,很巧,发现我们需要第3字节的数据刚好在地址1的位置,但是这个位置有两个字节啊,怎么办?CPU不用管,它只需要发出地址就行,所以这也是内存控制器做的事,内存控制器根据收到的CPU发送的 A0 = 1 ,那么读取的就是地址编号为1,右边的那个字节(第3字节)的数据。

当为32-bit ROM,我收到的是0x0,这个数值,也就是十进制的0,读取地址编号为0的地址的值,同样,发现我们需要第3字节的数据刚好在地址0的位置,但是这个位置有四个字节啊,怎么办?CPU不用管,它只需要发出地址就行,所以这也是内存控制器做的事,内存控制器根据收到的  [A1:A0]  =  11 ,那么读取的就是地址编号为0,最右边的那个字节(第3字节)的数据。

这个过程可以使用一张图来表示,就可以简单明了。


再举个例子:

假如现在执行指令:

mov  R0 ,#4    --把4的数值赋值给R0

LDR R1, [R0]  --读取地址为43的位置读取4个字节的数据

那他的过程可以简化为下表:

所以CPU就是一个大BOSS,它只负责发出指令(地址)给内存控制器,而关于你数据是如何拆分和组装的,CPU并不关心,我只需要发出指令,然后就收数据就好了,那剩下的事情就是手下(内存控制器)做的,它负责数据的组装和拆分,最终把正确的结果返回给CPU。


3.如何确定芯片的访问地址

1)、根据片选信号确定基地址(base)

2)、根据连接的地址线的条数来确定范围

由上图可知,NOR FLASH使用片选0,那么他的基地址就是0,DM9000使用片选4,基地址就是:0x200000000

SDRAM使用片选6,基地址是0x30000000

举例:

可以看一看 原理图中:NOR FLASH 、DM9000以及SDRAM分别用到几根地址线:

NOR FLASH :16-bit ROM

DM9000 :16-bit ROM

SDRAM :32-bit ROM

这个内存比较特殊,发出的地址分为行地址和列地址。

那么这几个设备的地址范围,如下表所示: 



4.分析读写NOR FLASH的读写时序

看一下S3C2440的数据手册中内存控制器这部分:

  • 读时序:

  • 写时序:

为什么他们需要通过编程来控制呢?

因为2440可以接很多种不同型号的内存芯片,这些芯片的性能有差别,有些性能很强,只要我发出读信号,立马就可以读取内存数据,这样就可以把等待时间减少,加快内存读取速度,假如性能比较弱的话就需要去调整时间了,所以说根据外接不同的芯片需要设置不同的时序时间,而这个时间需要CPU和外接内存时序匹配才可以,所以还需要看外接内存NOR FLASH 的读取时序图。

S3C2440如何能读写NOR FALSH的数据?

答:使2440发出的读写时序,满足NOR FLASH 的读写时序。

在这里使用NOR FALSH的芯片型号是:MX29LV160DBTI,查看他的数据手册。

先来看几个名词,时序图的交流特性

NOR FLASH 读时序:

现在我们可以设计一个程序去读NOR FLASH

我们的目的是需要2440的读时序能满足NOR FALSH的读时序、

为了方便,我们让2440同时发出片选信号,读信号,和地址信号。保持至少70ns的时间,也就是设置下图的Tacc大于70ns

其他的都可以默认设置为0

找到BANKCON0这个寄存器:

NOR FLASH 接在片选0,需要设置BANKCON0寄存器

位宽寄存器:采用默认设置


5.SDRAM初识

5.1.什么是SDRAM?

SDRAM:Synchronous Dynamic Random Access Memory,同步动态随机存储器。同步是指其时钟频率和CPU前端总线的系统时钟相同,并且内部命令的发送与数据的传输都以它为基准;动态是指存储阵列需要不断的刷新来保证数据不丢失;随机是指数据不是线性依次存储,而是自由指定地址进行数据的读写。
5.2.SDRAM内存芯片的内部结构
1).逻辑Bank与芯片位宽:
现在进行深入了解SDRAM的内部结构。这里主要的概念就是逻辑Bank。简单的说,SDRAM的内部是一个存储阵列。因为如果是管道式存储,就很难做的随机访问了。

阵列就如表格一样,将数据"填"进去,你可以把它想象成一张表格。和表格的检索原理一样,先指定一行(Row),再指定一列(Column),我们就可以准确的找到所需要的单元格,这就是内存芯片寻址的基本原理。对于内存,这个单元格可称为存储单元,那么这个表格叫什么呢?它就是逻辑Bank(简称L-Bank)。下图是存储阵列(L-Bank)示意图。

由于技术、成本等原因,不可能只做一个全容量的L-Bank,而且最重要的是,由于SDRAM的工作原理限制,单一L-Bank将会造成非常严重的寻址冲突,大幅度降低内存效率。所以人们在SDRAM内部分割成多个L-Bank,较早以前是两个,目前基本是4个,这也是SDRAM规范中的最高L-Bank数量。

这样,在进行寻址时就要先确定是那个L-Bank,然后再在这个选定的L-Bank中选择对应的行与列进行寻址。可见对内存的访问,一次只能一个L-Bank工作,而每次与内存控制器交换的数据就是L-Bank存储阵列中一个"存储单元"的容量。在某些厂商的表述中,将L-Bank中的存储单元称为Word。

而SDRAM内存芯片一次传输的数据量就是芯片的位宽,那么这个存储单元的容量就是芯片的位宽,但是要主要,这种关系仅对SDRAM有效。

2).SDRAM存储原理

L-Bank中的存储单元是基础的存储单位,它的容量是若干bit(对于SDRAM而言,就是芯片的位宽),而每个bit则是存放于一个单独的存储中。这些存储体就是内存中最小的存储单元。你可以用硬盘操作中的簇与扇区的关系来理解内存中的存储形式。扇区是硬盘中的最小存储单元,而每个簇则包含有多个扇区,数据的交换都是一个簇为单位进行(一次传输一个存储单元的数据)。

3).芯片的存储容量:

现在我们应该清除内存芯片的基本组织结构了。那么内存的容量怎么计算呢?显然,内存芯片的容量就是所有L-Bank中的存储单元的容量总和。计算有多少个存储单元和计算表格中单元数量的方法是一样的:

存储单元数量 = 行数 * 列数 * L-Bank的数量

在很多内存产品介绍文档中,都会用M*W的方式来表示芯片的容量。M是给芯片中存储单元的总数,单位是兆(应为写M),W代表每个存储单元的容量,也就是SDRAM芯片的位宽(Width),单位是bit。计算出来的芯片容量也是以bit为单位,但用户可以采用除以8的方法换算为字节(Byte)。比如8M*8,就是一个8bit位宽芯片,有8M个存储单元,总容量是64Mbit(8MB)。


5.3.读写SDRAM分析的时序分析

从上面几个章节的内容知道,CPU只负责发出地址给内存控制器,接着内存控制器根据发出的地址范围确定是哪个片选信号,比如SDRAM是接在片选6(nGCS6)上,那他的地址就是从 0x30000000 开始,使用了17位地址线那么寻址范围就是 2^17=128k

那么地址范围从:0x30000000  到  0x30020000 

2440芯片手册:

原理图:


 查看一下SDRAM型号为:EM63A165TS-6G 的数据手册:

这款芯片是一个位宽为16位,容量为32M的芯片。

内存芯片的容量
现在我们应该清楚内存芯片的基本组织结构了。那么内存的容量怎么计算呢?显然,内存芯片的容量就是所有 L-Bank 中的存储单元的容量总合。计算有多少个存储单元和计算表格中的单元数量的方法一样:

存储单元数量=行数×列数(得到一个 L-Bank 的存储单元数量)×L-Bank的数量

在很多内存产品介绍文档中,都会用 M×W 的方式来表示芯片的容量(或者说是芯片的规格/ 组织结构)。M 是该芯片中存储单元的总数,单位是兆(英文简写 M,精确值是 1048576,而不是 1000000),W 代表每个存储单元的容量,也就是 SDRAM芯片的位宽(Width),单位是 bit 。计算出来的芯片容量也是以 bit为单位,但用户可以采用除以 8 的方法换算为字节(Byte)。比如 8M×8,这是一个 8bit 位宽芯片,有 8M个存储单元,总容量是 64Mbit(8MB)。 

在外部通过把两块16bit的芯片拼接起来组成32bit的芯片,容量为64M,原理图如下:


因此数据在读写的时候CPU依然只是发送一个地址,根据地址发出片选信号,但是内存控制器做的事就很多了,它需要根据你外接的SDRAM进行设置,比如,外接芯片的位宽,容量,等,还需要分出几个逻辑块(L-BANK)以及分行(ROW)列(Colum),定义了这些通信格式之后才能找到相应的地址进行存储。

那么逻辑块、行地址、列地址如何拆分?

设置内存控制器的相关寄存器。

找到2440的芯片数据手册,查看内存控制这一章节

1).设置 SWSCON 设置为 : 0x02000000

其中 ST6这个位表示的含义:0 = Not using UB/LB (The pins are dedicated nWBE[3:0 )

 内存是32位宽度的,如果指向修改其中某个字节,但是,我仍然提供四个字节的数据,那如何只操作其中的某一个字节呢?

这时需要用到SDRAM的   nWBE[3:0],设置某个内存单元是否会被写。

如果只想读某个字节,我还是一次性读32bit(4字节数据),通过通过程序字节挑出想要的字节就行了。不需要屏蔽某个字节。

2).设置 BANKCON6

设置BANKCON6 =  0x18001

重要参数:

  • SCAN: 列位数

  • Trcd参数:

内存控制器是先发出行地址,再发出列地址,他们发送必须要有时间间隔,而这个参数就是规定行列地址发送的时间延时周期的。

举例:

列寻址信号与读写命令是同时发出的。虽然地址线与行寻址共用,但 CAS(Column Address Strobe,列地址选通脉冲)信号则可以区分开行与列寻址的不同,配合 A0-A9,A11(本例)来确定具体的列地址。

然而,在发送列读写命令时必须要与行有效命令有一个间隔, 这个间隔被定义为 tRCD,即 RAS to CAS Delay(RAS 至 CAS延迟),大家也可以理解为行选通周期,这应该是根据芯片存储阵列电子元件响应时间 (从一种状态到另一种状态变化的过程)所制定的延迟。tRCD 是 SDRAM的一个重要时序参数,可以通过主板 BIOS经过北桥芯片进行调整,但不能超过厂商的预定范围。广义的 tRCD以时钟周期(tCK,Clock  Time)数为单位,比如 tRCD=2 ,就代表延迟周期为两个时钟周期,具体到确切的时间,则要根据时钟频率而定,对于 PC100 SDRAM ,tRCD=2 ,代表 20ns 的延迟,对于 PC133则为 15ns。

 Trcd的具体数值,需要需要根据芯片数据手册来决定:

芯片手册规定,最大为20ns,而我们的时钟是 100M,那么一个时钟周期 t = 10ns,那么只需要两个时钟周期。

  •  2440寄存器描述: 

3).REFRESH寄存器

设置寄存器:REFRESH = 0x8404F5

重要概念和参数:

刷新
之所以称为 DRAM ,就是因为它要不断进行刷新 (Refresh)才能保留住数据,因此它是 DRAM最重要的操作。刷新操作与预充电中重写的操作一样, 都是用 S-AMP先读再写。

但为什么有预充电操作还要进行刷新呢?

因为预充电是对一个或所有 L-Bank 中的工作行操作,并且是不定期的,而刷新则是有固定的周期依次对所有行进行操作,以保留那些久久没经历重写的存储体中的数据。但与所有 L-Bank 预充电不同的是,这里的行是指所有 L-Bank 中地址相同的行,而预充电中各 L-Bank 中的工作行地址并不是一定是相同的。

那么要隔多长时间重复一次刷新呢?

目前公认的标准是, 存储体中电容的数据有效保存期上限是 64ms (毫秒,1/1000 秒),也就是说每一行刷新的循环周期是 64ms 。这样刷新速度就是:行数量/64ms。

例如:

我们在看内存规格时,经常会看到 4096 Refresh Cycles/64ms8192 Refresh Cycles/64ms 的标识,这里的4096与 8192就代表这个芯片中每个 L-Bank 的行数。刷新命令一次对一行有效,发送间隔也是随总行数而变化,4096 行时为 15.625μs(微秒,1/1000 毫秒),8192行时就为 7.8125μs。

刷新操作分为两种:自动刷新(Auto Refresh ,简称 AR)与自刷新(SelfRefresh,简称 SR)。不论是何种刷新方式,都不需要外部提供行地址信息,因为这是一个内部的自动操作。对于 AR, SDRAM内部有一个行地址生成器(也称20刷新计数器) 用来自动的依次生成行地址。 由于刷新是针对一行中的所有存储体进行,所以无需列寻址,或者说 CAS在RAS之前有效。所以,AR又称 CBR (CAS BeforeRAS,列提前于行定位)式刷新。由于刷新涉及到所有 L-Bank,因此在刷新过程中,所有 L-Bank 都停止工作, 而每次刷新所占用的时间为 9 个时钟周期(PC133标准),之后就可进入正常的工作状态,也就是说在这 9 个时钟期间内,所有工作指令只能等待而无法执行。64ms 之后则再次对同一行进行刷新,如此周而
复始进行循环刷新。 显然,刷新操作肯定会对 SDRAM的性能造成影响, 但这是没办法的事情,也是 DRAM相对于 SRAM (静态内存,无需刷新仍能保留数据)取得成本优势的同时所付出的代价。SR则主要用于休眠模式低功耗状态下的数据保存,这方面最著名的应用就是 STR(Suspend to RAM,休眠挂起于内存)。在发出 AR命令时,将 CKE置于无效状态,就进入了 SR模式,此时不再依靠系统时钟工作,而是根据内部的时钟进行刷新操作。在 SR期间除了 CKE之外的所有外部信号都是无效的(无需外部提供刷新指令) ,只有重新使 CKE有效才能退出自刷新模式并进入正常操作状态。

  • Refresh Counter: 

查看数据手册:

查看芯片手册的刷新周期是为:7.8 us

其实也就是说一共有8192 行(芯片中每个 L-Bank 的行数),必须在64ms之内刷新所有的行,所以刷新周期是:64ms/8192=7.8125us.

在上面知道列数一共有 9 bit,L-BANK 总共是4个,那么存储块的个数是: 2^9*8192*4=16777216.

每个存储块的大小是32bit(4字节),那么总容量就是:16777216 * 4 = 67108864 byte = 64M 

根据下面的公式计算  Refresh Counter 的值:

此处HCLK为100M,那么Refresh count = 2 11 + 1 - 100x7.8 = 1269  = 0x4F5

  • Trp:行预先充电时间

  • Trc:Row cycle time  行周期时间

而在数据手册中有 这样条公式:Trc=Tsrc+Trp,因为Trp = 20ns,因此可以设置 Tsrc = 45ns,也就是5个HCLK.


设置2440的寄存器,如下:


4).BANKSIZE 寄存器

设置 BANKSIZE  寄存器为: 0xb1

5).MRSRB6

设置MRSRB6 =  0x20

重要参数:

  • CL:CAS 潜伏期

数据输出(读)
在选定列地址后, 就已经确定了具体的存储单元, 剩下的事情就是数据通过数据 I/O 通道(DQ)输出到内存总线上了。但是在 CAS(列地址)发出之后,仍要经过一定的时间才能有数据输出,从 CAS与读取命令发出到第一笔数据输出的这段时间,被定义为 CL(CAS Latency,CAS 潜伏期)。由于 CL只在读取时出现,所以 CL又被称为读取潜伏期(RL,Read Latency)。CL 的单位与 tRCD一样,为时钟周期数,具体耗时由时钟频率决定。不过,CAS并不是在经过 CL周期之后才送达存储单元。实际上 CAS与 RAS一样是瞬间到达的, 但 CAS的响应时间要更快一些。

为什么呢?

假设芯片位宽为n 个 bit ,列数为 c,那么一个行地址要选通 n×c 个存储体,而一个列地址只需选通 n 个存储体。但存储体中晶体管的反应时间仍会造成数据不可能与 CAS在同一上升沿触发,肯定要延后至少一个时钟周期。由于芯片体积的原因, 存储单元中的电容容量很小, 所以信号要经过放大来保证其有效的识别性,这个放大/ 驱动工作由 S-AMP负责,一个存储体对应一个S-AMP通道。但它要有一个准备时间才能保证信号的发送强度(事前还要进行电压比较以进行逻辑电平的判断) ,因此从数据 I/O 总线上有数据输出之前的一个时钟上升沿开始,数据即已传向 S-AMP ,也就是说此时数据已经被触发,经过一定的驱动时间最终传向数据 I/O 总线进行输出,这段时间我们称之为 tAC(Access14 Time from CLK,时钟触发后的访问时间)。tAC 的单位是 ns,对于不同的频率各有不同的明确规定, 但必须要小于一个时钟周期, 否则会因访问时过长而使效率降低。比如 PC133的时钟周期为 7.5ns,tAC 则是 5.4ns。需要强调的是,每个数据在读取时都有 tAC,包括在连续读取中,只是在进行第一个数据传输的同时就开始了第二个数据的 tAC。

查看SDRAM芯片手册,查看具体可以设置的数值:

此处设置为第2个clock,表示CAS发出后,第二个clock,SDRM就会提供数据,这个值是会写入SDRAM芯片的一个寄存器(MR,mode regisiter)中保存起来的。

备注:本文的部分内容是从《高手进阶,终极内存技术指南——完整 / 进阶版》摘抄。 


6.编程读/写 SDRAM

从第五节可知,设置SDRAM需要设置的寄存器有5个,如下:

1).设置   SWSCON               设置        BWSCON= 0x02000000
2).设置   BANKCON6           设置        BANKCON6 =0x18001
3).设置   REFRESH              设置        REFRESH   = 0x8404F5
4).设置   BANKSIZE             设置        BANKSIZE = 0xb1
5).设置   MRSRB6                设置        MRSRB6 =  0x20​​​​​​​


6.1.新建一个C语言文件,sdram_init.c和 sdram_init.h

内容分别如下:

sdram_init.c:

#include "s3c2440_soc.h"
#include "myprintf.h"


void sdram_init(void)
{
	BWSCON	=0x02000000;
	BANKCON6=0x18001;
	REFRESH =0x8404f5;
	BANKSIZE=0xB1;
	MRSRB6	=0x20;

}

int sdram_test(void)
{
	//定义一个unsigned char 类型的指针,其地址是SRAM的起始地址
	volatile unsigned char *p =(volatile unsigned char *)0x30000000;
	int i;
	//写sdram,从地址0x3000000 到0x300003E8 写入值2000 到 0的值,间隔为2
	for(i =0;i<256;i++)
		p[i] = 255 -i;
	//读sdram
	for(i=0;i<256;i++)
	{
		//打印出写入的值,如果打印的值和我们写入的值是一致的那么写入成功
		myprintf("p[%d] = %d\n\r",i,p[i]);
		//如果地址0x3000000a的值不为 245,说明写入失败,返回 -1
		if(p[10] != 245)
			return -1;
	}
	//否则写入成功,返回 2 
	return 2;
}

解释:

sram_init函数是对SRAM进行初始化的配置,使2440发出的读写的时序能满足外部接的SDRAM的要求。

sram_test函数是对SRAM读写进行测试:

首先定义一个指针变量p,使它指向SRAM的起始地址,接着p所指向的地址开始写256个数据,每个字节存储一个数据,分别是从255到0. 

接着以p为其实地址从SDRAM读取256个字节的数据,然后使用myprintf打印出来(这个函数是自己在ARM平台上实现的,具体查看:点我查看)。

然后直接读取  p[10]的值,看如果这个值不等于254说明刚刚没有写入成功,否则写入成功。写入成功返回2,否则返回0.

sdram_init.h: 对初始化函数和测试函数的声明

#ifndef __SDRAM_INIT_H
#define __SDRAM_INIT_H

void sdram_init(void);
int sdram_test(void);

#endif

6.2编写主函数main.c

#include "s3c2440_soc.h"
#include "uart.h"
#include "sdram_init.h"
#include "led.h"
#include "myprintf.h"

int main()
{
	
	uart0_init();
	sdram_init();
	myprintf(
"Hello world!\r\n");
	while(1)
	{
		if(sdram_test() == 2)
		{
			
			led_test();
		}
		else return 0;
	}
	return 0;


}

备注:

1.初始化串口,然后初始sdram,接着打印Hello world 的字符串。

2.在循环中,调用sdram_test函数,如果返回值为2说明,sdram读写成功。则执行 led_test函数。这个是依次点灯的函数(详细点我:点我查看),只要sdram读写成功,就会执行这个点灯的程序,否则程序不运行。

6.3编写Makefile文件:

把所有的c文件和汇编文件生成目标文件(.o)然后连接在一起生成 sdram.bin 文件:

all:
	arm-linux-gcc -c -o led.o led.c
	arm-linux-gcc -c -o uart.o uart.c
	arm-linux-gcc -c -o lib1funcs.o lib1funcs.S
	arm-linux-gcc -c -o myprintf.o myprintf.c
	arm-linux-gcc -c -o sdram_init.o sdram_init.c
	arm-linux-gcc -c -o main.o main.c
	arm-linux-gcc -c -o start.o start.S
	arm-linux-ld -Ttext 0 -Tdata 0xbc0 start.o led.o uart.o sdram_init.o main.o myprintf.o lib1funcs.o -o sdram.elf
	arm-linux-objcopy -O binary -S sdram.elf sdram.bin	
	arm-linux-objdump -D sdram.elf > sdram.dis

clean:
	rm *.bin *.o *.elf *.dis

6.4.把所有文件上传到Linux系统,进行编译:

使用命令:make

6.5.把sdram.bin 文件传回window系统使用oflash进行烧录:

注意:此处做的事NOR FLASH 实验,必须烧录到NOR FLASH,并从NOR FLASH 启动。

烧录完成,打开串口,串口有数据打印,如下,共打印出256个数据,数值从255依次递减。可以看到p[10] = 254 

那么sdram_test()函数执行就会返回 2 

主函数再收到返回值为  2 ,则执行led_test()函数,可以看到开发版的灯在闪烁。


附录:源代码

下载地址:https://download.csdn.net/download/qq_36243942/10893997

发布了91 篇原创文章 · 获赞 247 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/qq_36243942/article/details/85596249