一般ARM芯片,都包含以下几类接口:
- GPIO、门电路
这类接口通过操作某些寄存器,来设置对应引脚为输入、输出引脚,以及其引脚的电平。 - 协议类接口
如UART、I2C、I2S、SPI等。CPU将数据写入某些地址和寄存器,对应引脚就会发出特定的波形。 - 内存接口
如Nor、SDRAM、DM9000(网卡芯片)等,通过读写不同的地址,可访问不同的外设。这些设备参与CPU的统一编址。
注意:Nand Flash不是内存接口,因为它是直接连接到Nand Flash控制器上的。
CPU把地址发送到内存控制器,内存控制器根据地址,将数据发送到不同的模块中,或从模块中读出数据,并返回给CPU。
GPIO以及协议类接口
,CPU发送的地址信号并不会传到外面,CPU只是用地址选择不同的寄存器,然后CPU把数据发送到对应的寄存器,CPU并不知道该寄存器的作用。而内存类接口
,CPU发送的地址可以直接传给这些设备。
一、内存控制器
S3C2440A芯片内部集成了内存控制器,几乎所有的外设和寄存器,都与它相连接,它们参与CPU的统一编址。cpu负责发出命令,其它的一切工作都交给了内存管理器。
那么内存管理器是如何来管理这些外设的呢?
1、地址空间被分为8个bank(bank0-bank7),每个bank对应128m地址空间。
2、每个bank拥有对应的片选引脚(nGCS0-nGCS7),当引脚为低电平时表示该bank被选通。
3、内存控制器根据不同的地址范围,发出不同的片选信号,只有被选中的芯片才会工作,没被选中的芯片就像不存在一样。
一个bank需要128M地址空间,需要27根地址线来表示。
那么8个bank,意味着外接内存类芯片可访问地址范围最多为1GB(128M*8)。
我们看到地址空间是0-0x40000000,总共是1G。那么s3c2440的cpu是32位的,它理论上可以使用的地址可以达到4G,除了用于连接外设的1G地址空间之外,还有一部分是cpu内部寄存器的地址,其余的地址没有用。其中cpu的内部寄存器的地址都在:0x48000000-0x5fffffff之间。
二、外接芯片的地址线接法
外接芯片的位宽不同,则地址线的接法也会不同。可以参考S3C2440A的第5章Memory Controller
,里面的ROM Memory Interface Examples
。
- 当芯片位宽为8bit时,地址线A0接外接芯片A0。此时外接芯片返回8bit数据到内存控制器,内存控制器直接将数据返回给CPU。
- 当芯片位宽为16bit时,地址线A1接外接芯片A0。此时外接芯片返回16bit数据到内存控制器,内存控制器通过地址线A0,选出这16bit中对应的8bit数据返回给CPU。
- 当芯片位宽为32bit时,地址线A2接外接芯片A0。此时外接芯片返回32bit数据到内存控制器,内存控制器通过地址线A0、A1,选出这32bit中对应的8bit数据返回给CPU。
例如:
ROM/bit | CPU发出地址 | ROM收到地址 | ROM返回数据 | 内存控制器挑选出数据给CPU |
---|---|---|---|---|
8bit(ROM) | 000011 | 000011 | 编号3的存储单元中的8数据 | 编号3的存储单元中的8bit数据 |
16bit(ROM) | 000011 | 000001 | 编号1的存储单元中的16数据 | 根据”A0=1”,挑出低8bit数据 |
32bit(ROM) | 000011 | 000000 | 编号0的存储单元中的32数据 | 根据“A0A1=11”,挑出最低8bit数据 |
三、时序图分析
查看s3c2440的PROGRAMMABLE ACCESS CYCLE
:
由时序图可知:
1、CPU先发出地址信号。
2、经过Tacs时间后再发出片选信号。
3、经过Tcos时间后发出读信号。
4、发出读信号后,数据才有效。
5、读出数据后,再移除释放读信号、片选信号、地址信号。
由我们的JZ2440电路图可知,Nor Flash为MX29LV160DBTI-70G
,然后查看其芯片手册。其交流特性如下:
参数 | 含义 |
---|---|
Taa | 发出地址数据后多久数据才有效 |
Tce | 发出片选信号后多久数据才有效 |
Toe | 发出读信号后多久数据才有效 |
四、开始编程
为了简单起见,我们设置片选信号、读信号、地址信号同时发出,然后保持70ns,再读取数据。就满足Nor Flash的时序了。
4.1、设置位宽
首先,设置BWSCON[2:1],用于设置bank0的位宽。
而DW0,是只读的,它是通过OM[1:0]引脚来决定的
查看硬件电路图:
可以发现,我们通过开关,决定了S3C2440A的启动方式:32bit Nor Flash 或 Nand-boot。
4.2、配置BANKCON0
我们Nor Flash接到Bank0,所以需要配置BANKCON0。
设备上电,采用晶振做为时钟源,12MHz,Tacc=0x111,14个周期。
14个周期=14/12MHz秒 = 14/12_000_000秒 = 14000/12纳秒 = 1166.6纳秒,远大于Nor Flash要求的70ns。所以当我们上电时,即使不设置时钟,设备也能正常运行。
而我们现在HCLK=100MHz:1个周期=10纳秒,Tacc只需设置为0x101,即8个时钟周期,就能满足Nor Flash大于70纳秒的时序要求。
4.3、所有源代码
init.c
#include "s3c2440_soc.h"
void bank0_tacc_set(int val)
{
BANKCON0 = val << 8;
}
init.h
#ifndef _INIT_H
#define _INIT_H
void bank0_tacc_set(int val);
#endif
led.c
#include "s3c2440_soc.h"
void delay(volatile int d)
{
while (d--);
}
int led_test(void)
{
/* 设置GPFCON让GPF4/5/6配置为输出引脚 */
GPFCON &= ~((3<<8) | (3<<10) | (3<<12));
GPFCON |= ((1<<8) | (1<<10) | (1<<12));
/* 循环点亮 */
int i = 4;
while (1) {
for(i=4; i<=6; i++) {
//对数据寄存器4~6位取反
GPFDAT ^= (1<
main.c
#include "s3c2440_soc.h"
#include "uart.h"
#include "init.h"
int main(void)
{
unsigned char c;
uart0_init();
puts("Enter the Tacc val: \n\r");
while(1)
{
c = getchar();
putchar(c);
if (c >= '0' && c <= '7')
{
bank0_tacc_set(c - '0');
led_test();
}
else
{
puts("Error, val should between 0~7\n\r");
puts("Enter the Tacc val: \n\r");
}
}
return 0;
}
start.S
.text
.global _start
_start:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* nor启动 */
ldr sp, =0x40000000+4096
bl main
halt:
b halt
uart.c
#include "s3c2440_soc.h"
/* 115200,8n1 */
void uart0_init()
{
/* 设置引脚用于串口 */
/* GPH2,3用于TxD0, RxD0 */
GPHCON &= ~((3<<4) | (3<<6));
GPHCON |= ((2<<4) | (2<<6));
GPHUP &= ~((1<<2) | (1<<3)); /* 使能内部上拉 */
/* 设置波特率 */
/* UBRDIVn = (int)( UART clock / ( buad rate x 16) ) –1
* UART clock = 50M
* UBRDIVn = (int)( 50000000 / ( 115200 x 16) ) –1 = 26
*/
UCON0 = 0x00000005; /* PCLK,中断/查询模式 */
UBRDIV0 = 26;
/* 设置数据格式 */
ULCON0 = 0x00000003; /* 8n1: 8个数据位, 无较验位, 1个停止位 */
/* */
}
int putchar(int c)
{
/* UTRSTAT0 */
while (!(UTRSTAT0 & (1<<2)));
/* UTXH0 */
UTXH0 = (unsigned char)c;
}
int getchar(void)
{
while (!(UTRSTAT0 & (1<<0)));
return URXH0;
}
int puts(const char *s)
{
while (*s)
{
putchar(*s);
s++;
}
}
uart.h
#ifndef _UART_H
#define _UART_H
void uart0_init();
int putchar(int c);
int getchar(void);
int puts(const char *s);
#endif
Markfile
all:
arm-linux-gcc -c -o led.o led.c
arm-linux-gcc -c -o uart.o uart.c
arm-linux-gcc -c -o init.o 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 start.o led.o uart.o init.o main.o -o nor.elf
arm-linux-objcopy -O binary -S nor.elf nor.bin
arm-linux-objdump -D nor.elf > nor.dis
clean:
rm *.bin *.o *.elf *.dis
编译烧写后,串口输入0~7,其中0~4,由于Tacc小于70ns,所以灯不会闪烁,只有5~7才会闪烁。
五、SDRAM配置
SDRAM:Synchronous Dynamic Random Access Memory,同步动态随机存储器。同步是指其时钟频率和CPU前端总线的系统时钟相同,并且内部命令的发送与数据的传输都以它为基准;动态是指存储阵列需要不断的刷新来保证数据不丢失;随机是指数据不是线性依次存储,而是自由指定地址进行数据的读写。
JZ2440接有2片SDRAM,每片SDRAM提供16位数据,如图所示:
其存储结构图如下:
它可以看作有多个表格(Bank)组成,并通过行地址和列地址来进行访问,每个小格子表示一个16位的存储单元。
- 1、CPU先发出片选信号,选中整个芯片。
- 2、发出Bank地址,选择哪个表格(Bank)。
- 3、然后依次发出行地址和列地址,才能定位到具体的存储单元。
这些信号都是由内存控制器发出,所以我们需要设置内存控制器。
我们查看电路图,知道SDRAM接到片选6上。
5.1、设置BANK6位宽
- 设置BWSCON[25:24]为10,因为JZ2440是由2个16为SDRAM芯片组成的32位SDRAM。
- BWSCON[26],是否使用WAIT信号,它是内存芯片向CPU发出来的。假设内存芯片非常慢,当CPU发出读写命令后,内存控制器开始驱动引脚,在这些时间里,内存芯片还没准备好数据,此时它可以向CPU发出nWAIT信号,请求CPU再宽限一点时间。
- BWSCON[27],设置SDRAM是否使用UB/LB引脚。
nBE:Byte Enable,表示读或者写的时候,是否可以通过操作这个引脚,来操作这个Byte。
nWBE:Write Byte Enable,表示写某个字节的时候,是否真正的写进去。
由于我们内存是32位(4字节)的,如果我们只想修改某个字节,此时就需要通过nWEB引脚,来决定某个字节会被写入。而读的时候,我们总是读出4字节数据,然后内存控制器从中挑选出我们所感兴趣的数据。
BWSCON = 0x02000000
5.2、设置BANK6位SDRAM
BANKCON6[16:15]决定Bank6接入的是ROM/SRAM、SDRAM,我们设置为0x11。只有外接成SDRAM时,地址才会被拆分成Bank地址、行地址、列地址。
JZ2440的SDRAM芯片型号是K4S561632N-LC75
,查看其芯片手册(K4S561632E):
知道其列地址有9条,即BANKCON6[1:0]=0x01
。
Trcd,表示内存控制器发出行地址多久后,才能发出列地址。查看SDRAM芯片手册的交流特性图:
知道Trcd最小取20ns,用于HLCK=100MHZ,即一个周期需要10ns,使用Trcd至少需要2个周期,即BANKCON6[3:2]=0x00
。
BANKCON6 = 0x00018001
5.3、设置SDRAM刷新寄存器
SDRAM不像静态SRAM那么可靠,在使用过程中,必须不断刷新它,不然数据会丢失。
- 设置REFEN = 1,表示开启SDRAM刷新。
- 设置TREFMD = 0,设置为自动刷新模式。
- Refresh Counter,刷新周期,查看SDRAM芯片手册,自动其刷新为
64ms refresh period (8K Cycle)
,即刷新周期为:
而我们HLCK恰好也等于100MHZ,刚好与实例一致,所以Refresh Count = 1269 = 0x4F5
。 - Trp,行地址的充电时间。由SDRAM芯片手册的交流特性图可知,Trp最小为20ns,所以
REFERSH[21:20]=0x00
。 - Tsrc,Trc = Tsrc + Trp。Trc为65ns,所以Tsrc=65-20=45ns。设置去0x01,即5个周期。
REFERSH = 0x008404f5
5.4、设置Bank寄存器
设置BANKSIZE[2:0]=001,因为SDRAM是由两个16位的32M芯片组成的32位64M芯片。
设置BANKSIZE[7]=1,允许突发访问,即同时可以连续一次访问多个字节。
设置BANKSIZE[5]=1,可以使用SCKE来使能休眠模式。
设置BANKSIZE[4]=1,推荐值。
BANKSIZE= 0x000000b1
5.5、设置SDRAM模式寄存器
除了CL,其余都是固定的。
CAS latency:内存控制器读SDRAM时,先发出Bank地址,再发出行地址,最后发出列地址。还要等一会,数据才好。
SDRAM芯片手册在FEATURES中告诉我们:CAS latency(2 & 3)
。当我们设置CL后,CPU会发送给SDRAM,SDRAM会将其保存在自身的寄存器中,当SDRAM收到列地址后,等待一段时间后,才会返回数据。
MRSRB6 = 0x00000020
5.5、开始编程
init.h
#ifndef _INIT_H
#define _INIT_H
void sdram_init();
int sdram_test();
#endif
init.c
#include "s3c2440_soc.h"
void sdram_init()
{
BWSCON = 0x02000000;
BANKCON6 = 0x00018001;
REFRESH = 0x008404f5;
BANKSIZE = 0x000000b1;
MRSRB6 = 0x00000020;
}
int sdram_test()
{
volatile unsigned char* p = (volatile unsigned char*)0x30000000;
int i;
for(i=0; i<1000; i++){
p[i] = 'A';
}
for(i=0; i<1000; i++){
if(p[i] != 'A'){
return -1;
}
}
return 0;
}
main.c
#include "s3c2440_soc.h"
#include "uart.h"
#include "init.h"
int main(void)
{
uart0_init();
sdram_init();
if(sdram_test()==0)
{
puts("ok\n\r");
led_test();
}else{
puts("error\n\r");
}
return 0;
}