通常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;
}
複製代碼