Chinaunix首页 | 论坛 | 博客
  • 博客访问: 180836
  • 博文数量: 80
  • 博客积分: 1400
  • 博客等级: 上尉
  • 技术积分: 835
  • 用 户 组: 普通用户
  • 注册时间: 2007-11-29 10:30
文章分类
文章存档

2009年(12)

2008年(60)

2007年(8)

我的朋友

分类: LINUX

2008-01-04 09:43:10

   最近在远峰公司买了arm9的板子,S3C2410,ARM920T,没有Nor flash,Nand Flash是64M,SDRAM是K9f1208,本人对linux的热情大于windows,所以想在linux下做开发,可是远峰公司只给我YFSJF.exe文件,而且没有源代码,每次在linux下编译好了后还得切换到windows下烧录,很是麻烦,于是在网上找了很多Jflash类似的程序,不过不同的烧录针对不同的硬件平台,Jflash是跟硬件紧密结合的,比如有的针对Nor Flash,有的针对Nand Flash的,不同内核有不同的Jflash,而且相同的内核也有不同的版本,因为Jtag的原理图不同,就只能有相对应的Jflash,程序中的定义要与pc机并口与Jtag接口的对应相一致。
在进入源码分析之前要介绍一些预备的知识,有助于理解源代码,毕竟这个程序和硬件联系很紧密的。首先介绍一下对PC并口做一些简要的介绍
一、PC标准配备并行口介绍
本文主要介绍计算机的标准配备并行端口即25针的母接头端口的应用,在此基础上可以运用相同的原理使用其它模式的并行端口。并行端口共有25支脚,但不是每支脚均被使用到。这些脚被区图1
25号
的端口寄存器,PC就是通过
状态、控制寄存器的读写访问并口的信号的。本文中使用一些通用的叫法,8个数据位分别为D0~D7,5个状态位为S3~S7,4个控制为C0~C3。其中字母表示了端口寄存器,数字则表示该信号在
寄存器中的位。


据,也可以读出数据(即可擦写);写进去的当然是我们希望从数据端口引脚输出的数据,不过读进来的也只是我们上次写进去的数据,或是原来保留在里面的数据,并不是从端口引脚输入PC的数据。数据端口引脚是PIN2~PIN9,其定义如下:
脚 信号 信号源 是否
0
Pin2
ATA0
PC

1
Pin3
DATA1
PC

2
Pin4
DATA2
PC

3
Pin5
DATA3
PC

4
Pin6
DATA4
PC

5
Pin7
DATA5
PC

6
Pin8
DATA6
PC

7
Pin9
DATA7
PC

般,我们就可以把它们当成是8个可以自由控制的输出点。当我们通过数据端口传送数据时,就是改变这8支脚的电平状态;而接受方也按照相同的编码原则解释,就可以获得传送的数据。

。除了S0以外,状态寄存器是只读的,读出数据信息是状态端口引脚上的逻辑状态。S0是支持EPP传输并口的超时标志信息,可以用软件方法清零。在许多并口中,状态输入接有上拉电阻。状态端口引脚是Pin10~Pin13、Pin15,其定义如下: 状态寄存器(即状态输
信号名 信号源
0
ime-Out
1
未使用
2
未使用
3
1
4
Pin13
Select
外设

5
Pin12
aperEnd
外设

6
Pin10
nAck
外设

7
Pin11
Busy
外设


的L
是专门用来传递打印机的状态的。和数据地址比较起来不一样的是,这里地址并非在连接器的脚位上均有对应点。在这个状态的显示上只有5个脚位有对应,位S0~S2是没有的--最起码是无法
让计算机有对应的值可读取。 如果打印机接到并口上,那么打印机的状态将会通过这几支脚传送到PC,程序只要去基地址 +1的位置读取数值即可
那么我们可以把这几支脚位拿来当作数字输入的通道;我们可以让这几支脚位的状态发生电位的改变,而利用程序去读取这些脚位的数值,即可实现数据的输入。


来说,这些位被用来输出,然而大多数SPP中,控制位为集电极开路/漏极开路模式,也就是说,它们同样可以用作输入。要从控制位上读取外部逻辑信号,首先将向相应的输出写入“1”,然后读取控制寄存器的值即可。但是,为了提高交换速度,大多数支持EPP和ECP接口中,控制位工作在不能用作输入的推拉模式下。在一些多模式接口中,控制位采用的是改进型的推拉模式,可以用作输入。控制端口引脚是Pin1、Pin14、Pin16和Pin17,其定义如下: 控制寄存器(即控制输出端口)基
引脚 信号名 信号源
0
Pin1
nStrobe
PC

1
Pin14
nAutoLF
PC

2
Pin16
nInit
PC

3
Pin17
electI
PC

4
IRQ
5
6
未使用
7
未使用
图4
是专门用来控制打印机动作的。 如同数据的送出,我们的程序只要将我们的信息送往(基地址+2)的地址去,就可以实现数据输出,接受端在相应引脚就可以接受到相应的逻辑电位状态。当控制端口的信号源为高电平时,这些引脚可以作为输入引脚,如同状态端口引脚一样。 在上述定义表格中,所谓“是否在连接器处倒相”是指并口硬件将连接器与相应寄存器位之间的4个信号进行了倒相处理。具体说来,S7、C0、C1、C3信号的逻辑状态在连接器处是与相应寄存器位反相的。当你对这些位进行写操作时,必须牢记写入的值应该与你想在连接器处设置的值相反;当要对这些位进行读操作时,也必须记住所读取的值与连接器处的值相反。 计算机的标准配备并行端口除以上介绍的数据端口引脚Pin2~Pin9、状态端口引脚Pin15、Pin10~Pin13、控制端口引脚Pin1、Pin14、pin16、Pin17外,连接器上的 其它引脚Pin18~Pin25是归地引脚GND。 在与Jtag相连接是控制寄存器是没有用到,只用到了数据
Pin2、Pin3、Pin8、Pin11分别对应数据寄存器的DATA0、DATA1、DATA6和状态寄存器的位7即信号名Busy。

源代码分析总是要针对
默认是从Nand Flash启动.下面的分析都是基于上面的配置,不同的配置可以做相应的修改,以满足不同的硬件环境。 在Jflash中有如下
Def.h Jtag.c Jtag.h K9sxx08.
我用的是Nand Flash K9f1208和K9s1208参数基本是一样的,两者可以通用。主函数在Sjf.
我们先从这里开始。 在linux下用Jflash-s3c
./Jflash-s3c2410 bo
OpenImageFile(char *filename); 它用来
bootloader.bin文件的大小,放在全局变量U32 imageSize;中、在函数void ErrorBanner(void)中只是介绍一些Jflash的使用说明,没有必要关注。 当执行到函数void OpenPpt(void)时,在该
中Ppt.c文件中定义的,在int GetValidPpt(void)函数中调用了int io_access_on( unsigned long port )函数,这个函数如下: int io_access_on(
{
perror ("ioperm()");
return 0;
}
if (
perror ("ioperm()");
return 0;
}
ret
}

#define LPT1 0x378 // the search order is LPT1 t
#define LPT2 0x278 // first valid address found is used (re-ord
#define LPT3 0x3bc // hardware base address for parallel port
ioperm(unsigned longrt , unsigned long num,bool on_off)这个函
是指的端口的基地址,num是连续的端口数目,on_off 表明是获得控制权(on_off=1),还是释放控制权(on_off=0)。 该函数是在include/a
#define __NR_ioperm 101
因为linux程序运行在保
制权。Linux的ioperm系统调用允许本地读/写访问系统的I/O端口,ioperm由于设计问题可允许非特权用户获得对系统I/O端口的读和写访问。当特权进程使用时,ioperm系统调用也会不正确地限制权限。 iop
存器端口。 在该函数中还
etValidPpt(void)中调用了_outp(),_inp(),它们其实是宏指令,在对端口进行存取后需要延迟1~2毫秒才能完成,而这些宏指令中会利用对端口0x80输出数据来达到延迟时间的目的,在对端口0x80输出数据时,并不影响系统,故先要取得对端口0x80得到控制权。 取得控制权后,向端口写入个数据0x55,在从该端口读出该数据
则通过函数void io_access_off( unsigned long port )释放对端口0x80和并口的控制权。 接下来调用函数
vo
{
//_outp(validPpt+ECP_ECR,ECR_STANDARD | ECR_D
DISSVCINT); }

由于双向并口是各制造商独自设计没有一个统一的规范。逐渐形成了如
1、原始并口SPP 单向8位+双向4位
2、简单双向PS/2 双向8位
3、增强并口EPP 高速双向
4、扩展并口ECP 高速双向可
5、多模式口ECP+EPP 可工作于多
在该函数中的语句是被注释了的,因为一般
有必要在设置了,如果你没有在主板BIOS中设置,那就的去掉注释了,但现在主板默认是EPP或ECP模式的。 接下来回到主
JTAG
AG定义了4个基本端口
out),TMS(Test model select),TCK(Test clock );分别实现测试数据的输入与输出,测试模式的选择以及提供测试时钟。此外,JTAG还定义了一个可选端口TRST,用于实现测试复位,在具体的电路中可能没有用到TRST,如图1中,就没有设计TRST(Test reset). 测试访问端口由五个专门引脚(四个输入和一个输出) 组成
(TRST # ) 引脚,因此它独立于其他设备。既使其他设备处于复位状态或无电状态,逻辑测试单元可以读或写自己的寄存器。 逻辑测试单元可
AP 控制器的操作。指令和数据由TDI 串行输送到TDO。
(2)TAP控制器:TAP控制器的作用是在TMS﹑TCK和TRST的作用
即在测试数据寄存器组和指令寄存器之间进行选择的信号;另一类是扫描控制信号,控制着测试数据寄存器与指令寄存器进入相应的状态。 (3)指令寄存器及译码逻辑:指令寄存器
在译码逻辑的作用下,产生译码信号。其产生的译码信号,主要有针对边界扫描单元的控制信号,以及在
多条测试数据寄存器中进行选择的选链信号。可以根据特殊的需要,定义新的译码信号。 (4)测试数据寄存器组:JTAG可以有很多测试数据寄存器,它们都具有扫描功能,称为扫描
链只不过是其中一条特殊的扫描链而已。用户可以定义新的扫描链及相应的指令。
JT
执行规定的操作。各引脚的功能如下:
·TCK:JTAG 测试时钟,为TAP控制器和寄
行移入或移出数据及指令。同时,TCK为TAP控制器状态机提供时钟.
·TMS :TAP控制器的模式输入信号。TCK的上升沿时刻TMS的状态确定
通常TMS引脚具有内部上拉电阻,以保证该引脚在没有驱动时处于逻辑1状态。
·TDI:JTAG指令和数据寄存器的串行数据输入端。TAP控制器的当前状态以及保存
指令决定对于一个特定的操作由TDI装入哪个寄存器。在TCK的上升沿时刻,TDI引脚状态被采 样,结果送到JTAG寄存器组。
·TDO: JTAG指令和数据寄存
指令决定对于一个特定的操作哪个寄存器的内容送到TDO输出。对于任何已知的操作,在TDI和
TDO之间只能有一个寄存器( 指令或数据)处于有效连接状态。TDO在TCK的下降沿改变状态,并且只
在 数据
·TRST:测试复位输入信号,低电平有效,为TAP控制器提供
J
TAP 控制器是一个有限状态机,
存器或数据寄存器,使其连接到TDI 和TDO 之间。状态机的初始状态是测试逻辑复位状(test-logic-reset)
图7 TMS为输入,在时钟TCK的驱动下进行状态转换,TAP控制器的16种状态转换图
指令/ 数据寄存器
逻辑测试单元有一个指令寄存器和三个数据寄存器,三个数据寄存器为:旁路寄存器(BYPASS) ,识
别码寄存器( IDCODE) ,边界扫描寄存器(BOUND) 。指令决定使用哪个寄存器。指令寄存器为4 位,指令由TDI 引脚串行输入。1 位旁路寄存器(BYPASS) 为TDI - TDO 提供了一个最短的串行通路。在板级测试期间,你可以通过这条通路绕开不需测试的设备。这可以提高测试速度。32 位识别寄存器( IDCODE) 提供了设备的制造商、版本号等信息。边界扫描寄存器(BOUND) 保持引脚的状态或引脚需要的数据。边界扫描寄存器为一个串行的移位寄存器,每个单元分配给芯片的相应引脚, 每一个独立的单元称为BSC (Boundary -Scan Cell) ———边界扫描单元。
指令简介
指令用于控制JTAG 口的操作,它包括基本指令和扩展指令。JTAG接口标准要求芯片支持的基本指令为: EXTEST , INTEST , SAMPLE/ PRELOAD ,BYPASS , IDCODE , HIGHZ。芯片厂商可以根据实际需要选择或添加扩展指令。S3c2410使用的是arm920t支持的指令有10条:
EXTEST
0000
SAN_N
0010
INTEST
1100
IDCODE
1110
BYPASS
1111
CLAMP
0101
HIGHZ
0111
CLAMPZ
1001
SAMPLE/PRELOAD
0011
RESTART
0100
在这些指令中,在时钟的上升沿对TDI和TMS进行采样,所有的输出TDO都是在时钟的下降沿进行的。在程序中只用到了EXTEST、IDCODE、SAMPLE/PRELOAD、BYPASS指令,上面三星的s3c2410使用的是arm920t核心,指令使用的是4位编码,但不一定所有的都是4位编码,如Intel StrongARM SA110处理器JTAG指令长度是5位,这个得查芯片手册,见后面的程序分析。
编程原理
通过JTAG口对Flash 编程的实质就是首先将EXTEST指令串行移入目标系统板上的JTAG 器件的指令寄存器,然后控制JTAG器件的TCK和TMS引脚使其进入Shift-DR 状态,再将数据串行移入到JTAG器件的边界扫描寄存器,最后控制JTAG器件的TCK 和TMS引脚使其进入Update-DR 状态将BSR 的内容写入Flash。指令装载完成后,只要不更改指令,就不需要再进行指令移位了,此时只需要进行数据移位,所以数据移位是编程的主要操作。
下面接着分析在Jtag.c中由void JTAG_ReadId(void)调用的函数void JTAG_Reset(void),它的目的是用JTAG单元从任何未知状态回到Test-Logic-Reset状态,当让TMS保持连续至少5个周期的高电平时,JTAG单元会从任何状态回到Test-Logic-Reset状态,在本程序中void JTAG_Reset(void)函数使TMS保持了6个周期的高电平,当然能使JTAG单元会从任何状态回到Test-Logic-Reset状态,后面延迟很关键,不同台式机cpu的频率不一样,所以延迟的时间是不一样。
复位进入Test-Logic-Reset状态后,接下来程序中频繁的调用了宏JTAG_SET()和JTAG_DELAY(),对于JTAG_SET()就是向并口输出,JTAG_DELAY()就是个延迟,必须要有延迟,否则可能不会成功,因为并口是低速设备,有比较长的时间延迟。
TDI_H 代表TDI位高电平、TDI_L代表TDI为低电平,其它的定义类似,有图7知道当处于Test-Logic-Reset状态时TMS为0,在时钟上升沿到来时,转移到Run-Test/Idel,在由Test-Logic-Reset状态转移到其他任何状态时,都要经过Run-Test/Idel状态,所以这里用了4个周期使其由Test-Logic-Reset状态转移到稳定的Run-Test/Idel状态,void JTAG_ReadId(void)就是先要把IDCODE移入JTAG,然后读取ID.
此时已经处于Run-Test/Idel状态,由图7当TMS=1时在时钟上升沿转移到Select-DR Scan Status,为什么是时钟上升沿转移,因为在JTAG内部使用的D边沿触发器,而且是上升沿触发的。当TMS=1,下个周期转移到Select-IR Scan Status,TMS=0时再转移到Capture-IR Status,最后在TMS=0转移到Shift-IR Status。然后移入IDCODE=1110, 注意移入的顺序,因为是右移先移入的是低位。下一步就是使其转移到到Update_IR状态,此时把ID写入到边界扫描寄存器,再将状态转移到Shift-DR状态,此时从TDO移位输出ID.
从并口读取ID时使用到了在Jtag.h中定义的宏
#define JTAG_GET_TDO() ( (InputPpt()&(1<<7)) ? LOW:HIGH )
它将读到的信号取反,因为在Jtag.h中有这样的定义:
// Pin Connections
// TCK :DATA[0] (2)
// TDI :DATA[1] (3)
// TMS :DATA[2] (4)
// TDO :STATUS[7] (11)
TDO和并口状态寄存器的位7相连,如图3,在该位使用了反相器,故读到的数据应取反后才能和TDO的输出相同。
ID是32位,在循环中读取了31位,在循环体外读取一位并同时将状态转移到Exit1_DR,因为在JTAG中使用个数据缓冲器,本次读到的是上次的输出信息。最后再将状态返回到Run-Test/Idel并把得到二进制字符数据转换成ID。
返回后再调用void S2410_InitCell(void)函数初始化边界扫描单元,在解释这个函数之前先介绍一下边界扫描。
边界扫描测试是通过在芯片的每个I/ O 脚附加一个边界扫描单元(BSC ,boundary scan cell) 以及
一些附加的测试控制逻辑实现的,BSC 主要是由寄存器组成的。每个I/ O 管脚都有一个BSC ,每个BSC 有两个数据通道:一个是测试数据通道,测试数据输入TDI ( test data input ) 、测试数据输出TDO(test data output) ;另一个是正常数据通道,正常数据输入NDI ( normal data input ) 、正常数据输出NDO(normal data output)
边界扫描链由若干具有相同结构的扫描单元组成,其基本结构与控制见图8。边界扫描单元的控制信号包括两类:一类是由TAP控制器产生的时序控制信号,另一类是由指令译码器产生的译码
图8扫描链及扫描单元结构与控制
扫描链的时序,大致可分为3个阶段。
(1)捕获阶段: 扫描单元从并行的数据输入,捕获数据到S触发器;
(2)移位阶段:扫描单元将S触发器中的值,串行移位到下一级扫描单元的S触发器中,并将其前一级扫描单元S触发器中的值移入;
(3)更新阶段:所有扫描单元并行地将其S触发器中的值,打入到U触发器中。为实现这3个阶段,需要TAP控制器产生的3个控制信号。此外,还需要一个指令译码信号,控制是否将U触发器中的值传入核心逻辑。
图9带有JTAG接口的芯片内部结构图
现在分析函数void S2410_InitCell(void)其目的是初始化边界扫描寄存器,它的数目叫边界扫描单元的长度。S3c2410边界扫描单元的长度为426,不同的型号有不同的长度,这个是在bsdl文件中有定义。
可以把边界扫描单元,即边界扫描链看着一个数组,在Pin2410.c有这样的定义
char outCellValue[S2410_MAX_CELL_INDEX+2];
char inCellValue[S2410_MAX_CELL_INDEX+2];
比如处理器的每一个引脚都有一个边界扫描单元,每一个引脚都是边界扫描链中的索引,s3c2410的引脚272,而扫描链的初始化即对处理器的引脚的赋值,且扫描链的长度426,当然还有对外围电路的测试单元,而且即使在程序中没有使用到的边界扫描单元也必须初始化。
在函数中初始化了边界扫描单元。在Pin2410.h中有这样的定义
#define S2410_MAX_CELL_INDEX 426 //0~426
#define DATA_CON (99)
#define DATA0_IN (100)
#define DATA0_OUT (98)
#define DATA1_IN (97)
#define nFCE (172)
#define nFWE (170)
#define nFRE (171)
#define nOE (147)
#define NCON0 (229)
#define nWAIT (167)
后面括号中的数字是边界扫描中的索引,也是对应处理器的引脚,这个是在bsdl文件中定义的,同一中型号的处理器是一样。具体怎样的对应关系,可以参看bsd文件的描述,下面简要介绍一下这个bsd文件
BSDL(boundary scan description language)语言硬件描述语言(VHDL )的一个子集,是对边界扫描器件的边界扫描特性的描述,主要用来沟通边界扫描器件厂商、用户与测试工具之间的联系,其应用包括:厂商将BSDL 描述作为边界扫描器件的一部分提供给用户;BSDL描述为自动测试图形生成(A T P G )工具测试特定的电路板提供相关信息;在BSDL 的支持下生成由IEEE1149.1标准定义的测试逻辑。现在,B S D L 语言已经正式成为IEEE1149.1标准文件的附件。BSDL本身不是一种通用的硬件描述语言,但它可与软件工具结合起来用于测试生成、结果分析和故障诊断。每一边界扫描器件都附有特定的BSDL 描述文件,下面举例说明。
这是SA1100的bsd文件,对边界扫描单元长度的定义:
attribute BOUNDARY_LENGTH of SA1100 : entity is 279
attribute INSTRUCTION_LENGTH of SA1100:entity is 5;指令长度(INSTRUCTION_LENGTH)属性定义了所有操作码的长度必须为6位。
attribute INSTRUCTION_OPCODE of SA1100 : entity is
"EXTEST (00000)," &
"SAMPLE (00001)," &
"CLAMP (00100)," &
"HIGHZ (00101)," &
"IDCODE (00110)," &
"BYPASS (11111)" ;
attribute INSTRUCTION_CAPTURE of SA1100 : entity is "00001" ;
上面是对JTAG指令的描述
attribute BOUNDARY_LENGTH of SA1100 : entity is 279 ; -- (ref B.8.14)
attribute BOUNDARY_REGISTER of SA1100 : entity is
------------------------------------------------------------------------------
-- scan cell cntr disable disable
-- cell type port function safe cell value state
------------------------------------------------------------------------------
"278 (BC_4, BATTF, INPUT, x), " &
"277 (BC_4, VDDFA, INPUT, x), " &
"276 (BC_4, *, internal,x), " &
"275 (BC_2, *, control, 0), " &
"274 (BC_1, SFRMC, OUTPUT3, x, 275, 0, Z ), " &
"67 (BC_1, WE, OUTPUT3, x, 68, 1, Z ), " &
"66 (BC_1, OE, OUTPUT3, x, 68, 1, Z ), " &
"65 (BC_1, RAS(3), OUTPUT3, x, 68, 1, Z ), " &
"64 (BC_1, RAS(2), OUTPUT3, x, 68, 1, Z ), " &
对边界扫描寄存器单元(BSC)属性的描述由单元号与4 个或7 个圆括号内的数据子段组成,这些
BSC 的排列顺序可以是任意的,但每个单元都必须被定义。单元号从0 到278(BOUNDARY_LENGTH-1)进行编号,0 单元是最靠近T D O 的单元。
如果有兴趣可以看看这个bsd文件,这个在程序中要用到宏定义要和bsd文件中定义相一致
#define S2410_MAX_CELL_INDEX 426 //0~426
#define DATA_CON (99)
#define DATA0_IN (100)
#define DATA0_OUT (98)
上面的定义要跟bsd文件中定义的一样,否则程序就不能正常运行。
char outCellValue[S2410_MAX_CELL_INDEX+2];存放的扫描单元输出的值,JTAG_RunTestldleState()调用使其转移到Run-Test/Idel状态,函数void JTAG_ShiftIRState(char *wrIR)的作用是把wrIR指向的指令移入指令寄存器shift-IR,移入后仍然使其处于Run-Test/Idle状态,对于Sample_Preload指令:对“连线”进行采样,进而进行输出观测;在此处即对426个边界扫描寄存器进行采样,注意输出TDO是使用并口的状态寄存器,其硬件连接有个反向器,然后输出的数据保存到char inCellValue[]数组中,然后用它初始化数组outCellValue.
在主函数中最后调的函数void K9Sxx08_Menu(void),在此函数void K9Sxx08_JtagInit(void)中首先将指令EXTEST(0000),此指令选择了测试模式中一条扫描链(扫描链有多条1,2,3,6……),默认选择是扫描链3,该程序使用的默认扫描链,如果要修改请参看DDI0151C_920T_TRM文件,EXTEST指令用一条已选的扫描链将TDI和TDO连接起来,
这样就可以对某个引脚进行赋值了。后面调用函数void S2410_SetPin(int index, char value),其目的给处理器与Nand Flash相连的控制引脚进行赋值。在Nand Flash手册中有这样的定义如下
图10 K9f1208引脚定义
void K9Sxx08_JtagInit(void)
{
JTAG_RunTestldleState();
JTAG_ShiftIRState(EXTEST);
S2410_SetPin(DATA_CON,HIGH); //D[7:0]=input
S2410_SetPin(nOE,HIGH);
S2410_SetPin(nFRE,HIGH);
S2410_SetPin(nFWE,HIGH);
S2410_SetPin(nFCE,HIGH);
S2410_SetPin(CLE,LOW);
S2410_SetPin(ALE,LOW);
}
初始化为地址锁存允许,命令锁存允许,读写都是不允许的,写保护是硬件连接到VDD的,始终都不允许写保护的。
在调用函数进行初始化Nand Flash
static void NF_Init(void)
{
NF_Reset();
//NF_nFCE_L();
NF_CMD(READ_1_1);
//NF_nFCE_H();
}
下面是k9f1208 flash操作命令
图11
当然不同Flash有不同的命令,这个要具体阅读flash的芯片手册
在调用static void NF_Reset(void)目的是使Nand Flash复位,需要注意的是在向flash读写时都有时间延迟,应做好相应的延迟,具体延迟的大小应查找相应的手册。
在static void NF_Init(void)中调用了static void NF_CMD(U8 cmd)函数,该函数是通过Nand Flash此地I/O0~I/O7写入,先写入控制命令,此次写入D[7:0]允许,芯片允许,写使能,地址锁存使能,这些参数都是在使用手册上的model selection中定义的,然后写入命令,此后禁止写信号以免再次写入数据将以前的命令覆盖。最后禁止芯片,禁止数据端口。
NF_nFCE_L()是使ce引脚输入0即使能芯片,NF_nFCE_H()则相反。
static U16 NF_CheckId(void)
{
U16 id;
NF_nFCE_L();
NF_CMD(0x90);
NF_ADDR(0x0);
Delay(1); //wait tWB(100ns)
id=NF_RDDATA()<<8; // Maker code 0xEC
id|=NF_RDDATA(); // Devide code 0x76
NF_nFCE_H();
printf("The Nand flash id is %ld\n",id);
return id;
}
查看flash手册如下:
图12 ID 读取时序
当先将Read ID 命令写入到I/O口,再将地址00写入到IO后,下两个时钟周期分别自动将ID送到IO输出,每次8位。static U8 NF_RDDATA(void)函数在两个时钟周期中分别读取0XECH,和0x76h,在此函数中调用了static void NF_ADDR(U8 addr)函数,目的是将addr写入到IO。对应的控制状态有2410_SetPin函数实现如下:
S2410_SetPin(DATA_CON ,LOW); //D[7:0]=output
S2410_SetPin(nFCE,LOW);
S2410_SetPin(nFRE,HIGH);
S2410_SetPin(nFWE,LOW);
S2410_SetPin(ALE,HIGH);
S2410_SetPin(CLE,LOW);
应于下图中的第5行相一致。
图13 模式选择K9f1208
这时到了程序最关键的地方了,通过调用函数void K9Sxx08_Program(void)对Nand Flash进行读写和擦除。
在讲对flash进行擦除之前,先了解一下Nand Flash
NAND Flash 的数据是以bit 的方式保存在memory cell,一般来说,一个cell 中只能存储一个bit。这些cell 以8 个或者16 个为单位,连成bit line,形成所谓的byte(x8)/word(x16),这就是NAND Device 的位宽。这些Line 会再组成Page,(Nand Flash 有多种结构,我使用的Nand Flash 是K9F1208,下面内容针对三星的K9F1208U0M),每页528Byte,每32 个page 形成一个Block, Sizeof(block)=16kByte 。1 block=16kbyte,512Mbit=64Mbyte,Numberof(block)=1024 1block=32page, 1page=528byte=512byte(Main Area)+16byte(Spare Area)
Nand flash 以页为单位读写数据,而以块为单位擦除数据。按照这样的组织方式可以形成所谓的三类地址: --Block Address -- Page Address --Column Address
对于NAND Flash 来讲,地址和命令只能在I/O[7:0]上传递,数据宽度是8 位。
512byte需要9bit来表示,对于528byte系列的NAND,这512byte被分成1st half和2nd half,各自的访问由地址指针命令来选择,A[7:0]就是所谓的column address。32 个page 需要5bit 来表示,占用A[13:9],即该page 在块内的相对地址。Block的地址是由A14 以上的bit 来表示,例如512Mb 的NAND,共4096block,因此,需要12 个bit 来表示,即A[25:14],如果是1Gbit 的528byte/page的NAND Flash,则block address用A[26:24]表示。而page address就是blcok address|page address in block NAND Flash 的地址表示为: Block Address|Page Address in block|halfpage pointer|Column Address 地址传送顺序是Column Address,Page Address,Block Address。 由于地址只能在I/O[7:0]上传递,因此,必须采用移位的方式进行。 例如,对于512Mbit x8 的NAND flash,地址范围是0~0x3FF_FFFF,只要是这个范围内的数值表示的地址都是有效的。以NAND_ADDR 为例: 第1 步是传递column address,就是NAND_ADDR[7:0],不需移位即可传递到I/O[7:0]上,而halfpage pointer 即bit8 是由操作指令决定的,即指令决定在哪个halfpage 上进行读写。而真正的bit8 的值是don't care 的。 第2 步就是将NAND_ADDR 右移9 位,将NAND_ADDR[16:9]传到I/O[7:0]上 第3 步将NAND_ADDR[24:17]放到I/O 上 第4 步需要将NAND_ADDR[25]放到I/O 上 因此,整个地址传递过程需要4 步才能完成,即4-step addressing。 如果NAND Flash 的容量是256Mbit 以下,那么,block adress 最高位只到bit24,因此寻址 只需要3 步。
下面,就x16 的NAND flash 器件稍微进行一下说明。 由于一个page 的main area 的容量为256word,仍相当于512byte。但是,这个时候没有所谓 的1st halfpage 和2nd halfpage 之分了,所以,bit8就变得没有意义了,也就是这个时候 bit8 完全不用管,地址传递仍然和x8 器件相同。除了,这一点之外,x16 的NAND使用方法和 x8 的使用方法完全相同。
一页为528字节,512字节存放代码和数据,其余的16字节用于ECC校验,文件系统信息,块损坏的记录。
下面分析这个最关键的函数,读写的原理都一样,故不再分析写页的函数。
static int NF_WritePage(U32 block,U32 page,U8 *buffer,U8 *spareBuf)
{
int i;
U32 blockPage=(block<<5)+page;
U8 *bufPt=buffer;
NF_nFCE_L();
NF_CMD(0x0);
NF_CMD(0x80); // Write 1st command
NF_ADDR(0); // Column 0
NF_ADDR(blockPage&0xff); //
NF_ADDR((blockPage>>8)&0xff); // Block & page num.
NF_ADDR((blockPage>>16)&0x01); //
for(i=0;i<512;i++)
{
NF_WRDATA(*bufPt++); // Write one page to NFM from buffer
}
if(spareBuf!=NULL)
{
for(i=0;i<16;i++)
{
NF_WRDATA(spareBuf[i]); // Write spare array(ECC and Mark)
}
}
NF_CMD(0x10); // Write 2nd command
Delay(1); //tWB = 100ns.
NF_WAITRB(); //wait tPROG 200~500us;
NF_CMD(0x70); // Read status command
Delay(1); //twhr=60ns
if (NF_RDDATA()&0x1) // Page write error
{
NF_nFCE_H();
printf("[PROGRAM_ERROR:block#=%d]\n",block);
NF_MarkBadBlock(block);
return 0;
}
else
{
NF_nFCE_H();
#if (WRITEVERIFY==1)
//return NF_VerifyPage(block,page,pPage);
#else
return 1;
#endif
}
}
上面已经介绍了Nand Flash是页进行读写的,以块进行擦写的。下面是写时序图
图14 K9f1208写时序图
先写入对flash写命令0x80,再分别一次写入行地址此处一般都是从A0~A7开始,写入A9~A16,A8是有指令决定的。不用关心再写入A17~A24,最后A25,对于k9f1208 A26~A31写入的必须是0
NF_CMD(0x0);
NF_CMD(0x80); // Write 1st command
NF_ADDR(0); // Column 0
NF_ADDR(blockPage&0xff); //
NF_ADDR((blockPage>>8)&0xff); // Block & page num.
NF_ADDR((blockPage>>16)&0x01); //
最后一句和NF_ADDR((blockPage>>16)&0xff)效果是一样的。接着写入数据,此时的数据是保存在缓冲寄存器中,等输入命令10h后,将缓冲寄存器中的值写入flash。
本函数后写入读取状态命令70h,此后一个周期从IO口位0输出状态,若为0表示写成功,否则失败
下面分析最后一个函数块擦除
// A[23:14][13:9]
// block page
static int NF_EraseBlock(U32 block)
{
U32 blockPage=(block<<5);
#if BAD_CHECK
if(NF_IsBadBlock(block) && block!=0) //block #0 can't be bad block for NAND boot
return 0;
#endif
NF_nFCE_L();
NF_CMD(0x60); // Erase one block 1st command
NF_ADDR(blockPage&0xff); // Page number=0
NF_ADDR((blockPage>>8)&0xff);
NF_ADDR((blockPage>>16)&0xff);
NF_CMD(0xd0); // Erase one blcok 2nd command
Delay(1); //wait tWB(100ns)
NF_WAITRB(); // Wait tBERS max 3ms.
NF_CMD(0x70); // Read status command
if (NF_RDDATA()&0x1) // Erase error
{
NF_nFCE_H();
printf("[ERASE_ERROR:block#=%d]\n",block);
NF_MarkBadBlock(block);
return 0;
}
else
{
NF_nFCE_H();
return 1;
}
}
在这个函数中要注意的是,擦除是以块进行的,写入地址的顺序为,先写开始的页地址,A9~A16,A17~A24,A25.
程序的结构与写页类似
NF_CMD(0x60); // Erase one block 1st command
NF_ADDR(blockPage&0xff); // Page number=0
NF_ADDR((blockPage>>8)&0xff);
NF_ADDR((blockPage>>16)&0xff);
NF_CMD(0xd0); // Erase one blcok 2nd command
故NF_ADDR函数调用了3次,写flash时调用了4次
其他的函数分析方法与上面的一致,关键是要先懂得各个部分的原理,本人遇到的问题及解决方法:
􀂾 在linux下运行Jflash-s3c2410是出现如下问题
+------------------------------------+ | SEC JTAG FLASH(SJF) v 0.11 + | modified by MIZI 2002.7.13 + +------------------------------------+ > flashType=5 ERROR: No CPU is detected(ID=0xffffffff). ERROR: K9S1208 is not detected. Detected ID=0xffff.
解决方法:
修改Jtag.h中的定义
#define TCK_H 0x01
#define TDI_H 0x02
#define TMS_H 0x04
#define TCK_L 0x00
#define TDI_L 0x00
#define TMS_L 0x00
与你的Jtag 原理图是否相匹配
这里的定义与图2的定义就不符合,需将#define TMS_H 0x04 该为#define TMS_H 0x40
􀂾 上面的问题解决后出现这样的问题
| SEC JTAG FLASH(SJF) v 0.11 + | modified by MIZI 2002.7.13 + +------------------------------------+ > flashType=5 > S3C2410X(ID=0x0032409d) is detected. > K9S1208 is detected. ID=0xec76 K9S1208 NAND Flash JTAG Programmer Ver 0.0 0:K9S1208 Program 1:K9S1208 Pr BlkPage 2: Exit Select the function to test :0 [SMC(K9S1208) NAND Flash Writing Program]
Source size: 0x73 Available target block number: 0~4095 Input target block number:0 target start block number =0 target size (0x4000*n) =0x4000 STATUS:Ep[PROGRAM_ERROR:block#=0] [Program error is occurred but ignored] [block #0 is marked as a bad block] [ERASE_ERROR:block#=1] [Program error is occurred but ignored] [block #1 is marked as a bad block] [ERASE_ERROR:block#=2]
是TCK频率的问题,TCK 的频率越低, 编程速度越慢; TCK 频率越高, 编程速度越快。但是每一个BS 器件都有一个TCK 频率的上限, 超过此频率, 信号就不稳定了,而且在对flash进行读写的时候也有延迟。故在程序中都要使用延迟。
􀂾 注意bsd文件的引脚定义和程序的定义是否一致,我的bsdl描述的和程序中一致,因此我并没有修改。
后叙:
已经写得很罗嗦了,我写的本意是希望每个人都能看得懂,这篇文章记录的本人遇到问题解决问题的过程,对一些专业术语的描述可能不恰当,希望批评指正和体谅,当然也不可能将每个细节都讲到,不过主要的问题和应该注意的我都有讲到,如果有什么问题,可以发邮件到,大家可以相互讨论,共同进步,这问题得以解决离不开网上热心朋友得帮助,在此非常感谢,也有很多地方借鉴了网上的文章,由于非常零散,不能一一列举。如果各位能从这篇文章获得帮助,我就非常满意了。同时也希望各位得经历与大家共同分享,因为分享是快乐的,不信你试试~
阅读(2328) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~