通常ARM芯片,都包含如下几类接口:html
一、GPIO、门电路前端
这类接口经过操做某些寄存器,来设置对应引脚为输入、输出引脚,以及其引脚的电平。linux
二、协议类接口编程
CPU将数据写入某些地址和寄存器,对应引脚就会发出特定的波形。如:UART、I2C、I2S、SPI等。markdown
三、内存接口app
CPU经过地址能够直接访问这类设备,由于它们都参与CPU的统一编址。如:Nor、SDRAM、DM9000(网卡芯片)等,dom
注意:Nand Flash不参与CPU的统一编址,由于它是直接链接到Nand Flash控制器上的。
异步
S3C2440A芯片内部集成了内存控制器,几乎全部的外设和寄存器都与它相连。CPU只负责发出命令,其他都交给了内存管理器处理。那么内存管理器是如何来管理这些外设的呢?测试
咱们能够查看s3c2440的地址空间分布
。ui
128M
地址空间。一个bank对应128M地址空间,须要27根地址线来表示。
那么8个bank,意味着外接内存类芯片可访问地址范围最多为1GB(128M*8)。
咱们看到地址空间是0-0x40000000,总共是1G。而s3c2440的cpu是32位的,它理论上可使用的地址能够达到4G,除了用于链接外设的1G地址空间以外,还有一部分是cpu内部寄存器的地址(0x48000000-0x5fffffff),其他的地址没有使用。
外接芯片的位宽不一样,则地址线的接法也会不一样。能够参考S3C2440A的第5章 Memory Controller
中的ROM Memory Interface Examples
。例如:
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
:
时序图流程以下:
由咱们的JZ2440电路图可知,Nor Flash为MX29LV160DBTI-70G
,而后查看其芯片手册。其交流特性以下:
参数 | 含义 |
---|---|
Taa | 发出地址数据后多久数据才有效 |
Tce | 发出片选信号后多久数据才有效 |
Toe | 发出读信号后多久数据才有效 |
为了简单起见,咱们设置片选信号、读信号、地址信号同时发出,而后保持70ns,再读取数据,这样就知足Nor Flash的时序要求了。
首先,设置BWSCON[2:1],用于设置bank0的位宽。
而DW0是只读的,它是经过OM[1:0]引脚来决定的
查看硬件电路图:
发现OM[1]直接接地,而OM[0]的电平,由Nor boot switch决定。咱们经过开关,决定了S3C2440A的启动方式:32bit Nor Flash
或 Nand-boot
。
咱们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纳秒的时序要求。
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<<i);
delay(30000);
}
}
return 0;
}
复制代码
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:Synchronous Dynamic Random Access Memory,同步动态随机存储器。同步是指其时钟频率和CPU前端总线的系统时钟相同,而且内部命令的发送与数据的传输都以它为基准;动态是指存储阵列须要不断的刷新来保证数据不丢失;随机是指数据不是线性依次存储,而是自由指定地址进行数据的读写。
通常SDRAM的存储结构以下:
它由多个表格(Bank)组成,并经过行地址和列地址来进行访问,每一个小格子表示一个存储单元,单元的大小等于SDRAM的位宽。
查看JZ2440的SDRAM电路图:
能够发现:
16位
的数据,容量为32M
,共计64M,32位。片选6
引脚,也就是说SDRMAM在第六个Bank地址空间中。BA[0:1]
分别接在ADDR[25:24]
上,用于选中SDRAM里的哪一个Bank。JZ2440的SDRAM芯片型号是K4S561632N-LC75
,查看其芯片手册(K4S561632E):
发现如下有用信息:
4M * 16bit * 4Bank = 32M
。参考SDRAM BANK ADDRESS PIN CONNECTION EXAMPLE
,能够知道Bank Address为A[25:24]
,对比JZ2440的SDRAM电路图,确实是BA[0:1]
分别接在ADDR[25:24]
上。
如今能够配置SDRAM,来测试SDRAM的读写操做。
首先咱们须要设置内存控制器,让Bank6设置为SDRAM,这样内存控制器在发送地址时,才会将其拆分为Bank地址、行地址和列地址。
BANKCON6[16:15]决定Bank6接入的是哪一种类型的内存芯片,这里咱们设置为SDRAM,即BANKCON6[16:15]=0x11
。
因为外接的SDRAM列地址有9条,因此BANKCON6[1:0]=0x01
。
Trcd,表示内存控制器发出行地址多久后,才能发出列地址。
查看SDRAM芯片手册的交流特性图:
知道Trcd最小取20ns,用于HLCK=100MHZ,即一个周期须要10ns,使用Trcd至少须要2个周期,即BANKCON6[3:2]=0x00
。
BANKCON6 = 0x00018001
复制代码
nBE:Byte Enable,表示读或者写的时候,是否能够经过操做这个引脚,来操做这个Byte。
nWBE:Write Byte Enable,表示写某个字节的时候,是否真正的写进去。
因为咱们内存是32位(4字节)的,若是咱们只想修改某个字节,此时就须要经过nWEB引脚,来决定某个字节会被写入。而读的时候,咱们老是读出4字节数据,而后内存控制器从中挑选出咱们所感兴趣的数据。
BWSCON = 0x02000000
复制代码
SDRAM不像静态SRAM那么可靠,在使用过程当中,必须不断刷新它,否则数据会丢失。
64ms refresh period (8K Cycle)
,即刷新周期为:而咱们HCLK=100MHz
,恰好与实例一致,因此Refresh Count = 1269 = 0x4F5
。
REFERSH[21:20]=0x00
。REFERSH = 0x008404f5
复制代码
BANKSIZE= 0x000000b1
复制代码
除了CL,其他都是固定的。
CAS latency:内存控制器读SDRAM时,先发出Bank地址,再发出行地址,最后发出列地址。还要等一会,数据才好。
SDRAM芯片手册在FEATURES中告诉咱们:CAS latency(2 & 3)
。当咱们设置CL后,CPU会发送给SDRAM,SDRAM会将其保存在自身的寄存器中,当SDRAM收到列地址后,等待一段时间后,才会返回数据。
MRSRB6 = 0x00000020
复制代码
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;
}
复制代码