目录
一:介绍
1:引入
2:概念
3:通信
A:片选信号
B:片选信号的地址空间范围
4:地址线
A:不同位数的接法
B:访问原理
C:访问地址
5:时序
1:NOR FLASH
A:2440NOR FLASH时序
B:原理/时序图
C:寄存器
6:SDARM
A:访问方式
B:原理图
C:BWSCON
D:BANKCON6
E:REFRESH
F:ANKSIZE
G:MRSR
二:代码
1:NOR FLASH
2:SDARM
一:介绍
1:引入
操作GPIO: 我们可以编写程序,让CPU去访问里面的寄存器
eg:GPFCON配置寄存器 . 可以设置这个寄存器,把某个引脚设置为输出或者输入; GPFDATA数据寄存器, 可以设置某个引脚发出高低电平.
UART一样我们可以写程序让CPU访问某些特定的寄存器.
对于CPU来说,我不关心你这些具体的接口,我只是去操作某个寄存器。我CPU把某些值写给某些寄存器,由这些对应的控制器发出特定的波形。所以这个时候我们的内存控制器就来了
2:概念
CPU发出的地址信号并不会传到外面来(eg:GPIO口)。CPU只是用地址来选择里面的不同寄存器。然后CPU把数据发给里面的寄存器,至于这些数据,这些地址会起什么作用,完全由里面的控制器决定。CPU发出来这些信号呢,并不会直接的输出到外部电路去。凡事都有例外(内存接口).
内存接口:对于这种接口的设备CPU发出来的地址,可以直接传给这些设备。
在原理图种也可以看到 数据信号CPU发出的地址和数据直接传给这个芯片
内存控制器作用:
3:通信
A:片选信号
内存控制器根据CPU发出的地址,设置不同的片选引脚,只有被选中的芯片才会工作,没有选中的芯片不会影的他
只有使得某个片选引脚输出低电平的时候,对应的芯片才会开始工作,
SDRAM:
在SDRAM中只有LnWE, 所以LnWE负责写入数据和读取数据
NOR FLASH
LnOE:拉低负责读取数据, LnWE负责写入数据
DM900:
LnOE:拉低负责读取数据, LnWE负责写入数据
CPU根据指令发出地址信号,内存控制器来根据这些地址来决定选中哪一些芯片。
B:片选信号的地址空间范围
每个片选引脚相隔134217758位, 当地址处于某一个范围的时候, 对于的片选信号才会有效----见4地址线C:访问地址
至少需要A0~A27条地址线:
内存的存储单元个数=2^地址线的条数,因此计算内存的总地址线数的公式为: 地址线的条数=log2(内存的存储单元个数.
CPU有32位地址线, 但是内存控制器他的能力只能控制27位地址线
只有使得某个片选引脚输出低电平的时候,对应的芯片才会开始工作,
4:地址线
A:不同位数的接法
8位
CPU的A0接到芯片的A0
16位
当使用两个8位的芯片拼接(DATA0-7接一个芯片,DATA8-15接一个芯片,也就是组成了一个16位的芯片)的时候,CPU的A1接芯片的A0(CPU的A0用来判断高8位有效还是低8位有效)
CPU的A1接的位芯片的A0
32位
CPU的A2接到芯片的A0
B:访问原理
CPU把地址发送给内存控制器, 内存控制器找到了相应地址的内存类设备(ROM), 内存类设备(ROM)返回数据给内存控制器, 内存控制器找到了储存单元种的数据发送给CPU.
内存控制器所发出的地址线, 有一部分接到了芯片上. 有一部分没有接接到芯片上。
接到了芯片上的这些引脚呢用来确定,读取这个芯片上哪一个单元,把这个单元返回给内存控制器。而内存控制器呢,会使用那些没有接的引脚来确定从这个单元中取出哪一个字节返回给CPU
eg:
C:访问地址
5:时序
1:NOR FLASH
我们这里面只研究读时序
A:2440NOR FLASH时序
Taa: 发出add后时间DATA才有效, 可以看到max时间为70ns, 如果在70ns之内访问数据,数据可能不太稳定, 导致我们读取数据错误.
Tce: 和上面的差不多, 片选信号的max为70
所以我们为了简单可以把地址,片选,读写,数据信号以70ns一起发出
B:原理/时序图
我们可以看到NOR FLASH有21根地址信号(但是CPU只连接芯片的20根), 16个数据信号, 以及片选限号NCS0, 和写信号LnWE读信号LnOE
我们可以计算一下我们这个2440NOR FLASH内存的大小
2^21/1024/1024=2MB
先发出地址信号,经过Tacs时间发出片选信号(nGCS),然后经过Tcos时间发出读信号,过一会数据才有效,把数据读走,然后把读信号释放掉,把片选信号释放掉,然后才开始新的地址周期
Tacs ,Tcos,Tacc,Tacp...这些需要根据不同的芯片来进行设置因为J2440可以接不同类型的内存类芯片,这些内存内芯片性能不同,所以所用的时间周期也不同。
C:寄存器
在上次种我们把HCLK该为了100MHZ
他默认为14clocks(14*10=140>70), 所以每次时序都可以正常的运行
6:SDARM
A:访问方式
不像NOR FLSAH一样直接发送地址, SDRAM拆分地址在发送
B:原理图
SDARM是由2个16为的芯片组合而成, 成为32位的芯片。 故内存大小
2^32=4294967296bit=4194304KB=4096MB=4GB
挂载在BANK6上,片选引脚为nGCS6
C:BWSCON
因为我们的SDRAM只挂载在BANK6上, 所以我们只关系BANK6。
DW6: 我们的bank是32位的所以选择---10
WS6: WAIT是由芯片发送给CPU的信号, 以前的芯片准备数据慢, 在CPU给内存控制器发送地址后。内存控制器给给芯片发送各种信号,准备采集数据,但是芯片如果还没有准备好数据可以给CPU发送WAIT信号。最大的宽限时间。 等待信号的使能,由于现在的内存速度足够快,在读数据时不需要内存控制器等待,选择0。
ST6:BANK6的带宽,选择32位
BWSCON=0x02000000
D:BANKCON6
MT: 我们使用的位SDRAM,使用选择11
Trcd:这个域的配置表示的是发送行有效命令到列有效命令之间的时间间隔。在发送列读写命令时必须要与行有效命令有一个间隔,这个间隔被定义为tRCD,即RAS to CAS Delay(RAS至CAS延迟),可以理解为行选通周期,这应该是根据芯片存储阵列电子元件响应时间(从一种状态到另一种状态变化的过程)所制定的延迟。
我们使用HCLK为10ns,所以选择00
SCAN:列地址的位数 选择01 --9位
BANKCON6=0x00018001
E:REFRESH
之所以称为DRAM,就是因为它要不断进行刷新(Refresh)才能保留住数据,因此它是DRAM最重要的操作
REFRE:肯定要使能刷新操作。---1
TREFMD:刷新操作分为两种:自动刷新(Auto Refresh,简称AR)与自刷新(Self Refresh,简称SR)。对于AR, SDRAM内部有一个行地址生成器(也称刷新计数器)用来自动的依次生成行地址。SR则主要用于休眠模式低功耗状态下的数据保存,这方面最著名的应用就是STR(Suspend to RAM,休眠挂起于内存)。这里不使用低功耗模式,故TREFMD=0B,使用自动刷新。-----0
Trp:预充电时间。其实就是读取不同行之间的时间间隔。L-Bank关闭现有工作行,准备打开新行的操作就是预充电(Precharge)。在发出预充电命令之后,要经过一段时间才能允许发送RAS行有效命令打开新的工作行,这个间隔被称为tRP(Precharge command Period,预充电有效周期)。-----00即20ns
Tsrc:--01 Trc我们取70ns, Trp我们设置的为20ns, 所以Tsrc=Trc-Trp=70-20=50ns
Refresh Conuter:我们根据SDARM芯片的Refresh period(刷新周期)的值确定刷新计数器的值。
64ms/8192bit=7.8ms
Refresh count = 2^11 + 1 100x7.8 = 1269=0x4f5---以为我们使用的HCLK为100MHZ
REFRESH=0x008404f5
F:ANKSIZE
BURST_EN:使能突发传输
突发(Burst)是指在同一行中相邻的存储单元连续进行数据传输的方式,连续传输所涉及到存储单元(列)的数量就是突发长度(Burst Lengths,简称BL)。
BURST_EN :使能他,一次访问多个字节SCKE_EN :使用时钟使能休眠模式
ANKSIZE=0x000000b1
G:MRSR
CL:
WBL:突发长度选择固定,第二个值为reserved,也没得选
TM:只能选00B
CL:CL是SDRAM很重要的参数在选定列地址后,就已经确定了具体的存储单元,剩下的事情就是数据通过数据I/O通道(DQ)输出到内存总线上了。但是在CAS发出之后,仍要经过一定的时间才能有数据输出,从CAS与读取命令发出到第一笔数据输出的这段时间,被定义为CL(CAS Latency,CAS潜伏期)。由于CL只在读取时出现,所以CL又被称为读取潜伏期(RL,Read Latency)。CL的单位与tRCD一样,为时钟周期数,具体耗时由时钟频率决定。
设置CL为2个时钟周期。CL=010B。
BT:只能选0
BL:只能选0
MRSR=0x00000020
二:代码
1:NOR FLASH
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
ldr r0, =0x4C000018
ldr r1, =(0<<9)
str r1, [r0]
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*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/*设置内存: sp栈* 我们判断是nor启动还是nand启动/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
bl main
halt:
b halt
#define ULCON0 (*((volatile unsigned int*)0x50000000))
#define UCON0 (*((volatile unsigned int*)0x50000004))
#define UBRDIV0 (*((volatile unsigned int*)0x50000028))
#define GPHCON (*((volatile unsigned int*)0x56000070))
#define GPHUP (*((volatile unsigned int*)0x56000078))
#define UFCON0 (*((volatile unsigned int*)0x50000008))
#define UTRSTAT0 (*((volatile unsigned int*)0x50000010))
#define UTXH0 (*((volatile unsigned char*)0x50000020))
#define URXH0 (*((volatile unsigned char*)0x50000024))
#define BANKCON0 (*((volatile unsigned int*)0x48000004))
#define GPFCON (*((volatile unsigned int*)0x56000050))
#define GPFDAT (*((volatile unsigned int*)0x56000054))
int LED_on()
{ /*设置寄存器 点亮LED2*/
/*设置输出模式*/
GPFCON = 0x400;
/*输出低电平*/
GPFDAT = 0;
return 0;
}
void init_nc_tacc(int val)
{
BANKCON0 = val << 8;
}
void UART_init()
{
/*引脚设置*/
GPHCON &= ~((3 << 4) | (3 << 6));
GPHCON |= ((2 << 4) | (2 << 6));
GPHUP &= ~((1 << 2) | (1 << 3)); /* 使能内部上拉 */
/*设置波特率---设置波特率位115200*/
/*
UBRDIVn = (int)(selected clock / (baudrate x 16) ) –1
我们使用我们的FCLK作为我们的时钟源--在汇编中是50MHZ
UBRDIVn=(5000 0000 /(115200*16))-1=26
*/
UCON0 = 0x00000005;
UBRDIV0 = 26;
/*数据格式*/
ULCON0 = 0x00000003; //不能写为这个ULCON0 |= (3 << 1)因为还要关注其他位;/*数据位=8,停止位=1,无奇偶校验 8n1*/
}
int putchar(int c)
{
/*发送数据*/
while ((UTRSTAT0 & (1 << 2)) == 0); //while (!(UTRSTAT0 & (1 << 2)));
UTXH0 = (unsigned char)c;
}
int getchar(void)
{
/*接收数据*/
while ((UTRSTAT0 & (1 << 0)) == 0); //while (!(UTRSTAT0 & (1 << 0)));
return URXH0;
}
int puts(const char* s)
{
while (*s)
{
putchar(*s);
s++;
}
}
#include "uart.h"
#include "sc2440_so.h"
#include "NOR.h"
#include "led.h"
int main(void)
{
unsigned char c;
UART_init();
puts("Please enter the numbesr:\n\r");
while (1)
{
c = getchar();
putchar(c);
if (c >= '0' && c <= '7')
{
init_nc_tacc(c-'0');
LED_on();
}
else
{
puts("Error: The number you entered is not between 0 and 7\n\r");
puts("Please rewrite input\n\r");
}
}
return 0;
}
init_nc_tacc(c-'0');和init_nc_tacc(c);的区别
在编程中,
init_nc_tacc(c-'0');
和init_nc_tacc(c);
的区别取决于init_nc_tacc
函数如何定义和c
的数据类型。
如果
c
是一个字符型变量(如char
),并且代表一个数字字符(例如'1'
、'2'
、'3'
等):
c-'0'
通常用于将字符型数字转换为实际的整数。在ASCII编码中,数字字符'0'
到'9'
是连续的,因此通过减去'0'
(其ASCII值为48)可以将字符型数字转换为整数。例如,如果c
是'3'
,c-'0'
将得到整数3
。init_nc_tacc(c-'0');
会将转换后的整数传递给函数。init_nc_tacc(c);
则会将字符型变量传递给函数。如果
c
不是代表数字的字符,那么c-'0'
可能没有明确的意义,并且可能不会得到预期的结果。如果
init_nc_tacc
函数对字符和整数有不同的处理逻辑,那么两个调用会有不同的效果。如果
c
是其他数据类型,比如已经是一个整数,那么c-'0'
就没有意义,并且可能导致编译错误或不可预测的行为。
2:SDARM
代码大致和NOR FLASH不变, 我们这里只展示改变的
#include "sc2440_so.h"
void SDARMA_init(void)
{
BWSCON = 0x02000000;
BANKCON6 = 0x00018001;
BANKCON7 = 0x00018001;
REFRESH = 0x008404f5;
ANKSIZE = 0x000000b1;
MRSRB6 = 0X00000020;
MRSRB7 = 0X00000020;
}
int init_test(void)
{
//片选引脚为nGCS6
volatile unsigned char* p = (volatile unsigned char*)0X30000000;
int i;
//write 操作
for (i = 0; i < 1000; i++)
{
p[i] = 0x55;
}
//读操作
for (i = 0; i < 1000; i++)
{
if (p[i] != 0x55) {
return -1;
}
}
return 0;
}
#include "uart.h"
#include "sc2440_so.h"
#include "NOR.h"
#include "led.h"
int main(void)
{
unsigned char c;
UART_init();
puts("Please enter the numbesr:\n\r");
while (1)
{
c = getchar();
putchar(c);
if (c == '1') {
SDARMA_init();
if(init_test() == 0)
{
LED_on();
}
}
else
{
puts("Error: Please retype:\n\r");
}
}
return 0;
}