目录
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/64ms 或 8192 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()函数,可以看到开发版的灯在闪烁。