随笔 - 60, 文章 - 0, 评论 - 197, 引用 - 0
数据加载中……

学习 ARM 系列 -- FS2410 开发板上 Nand Flash 到内存的代码搬移

一、目的
   前面做过一个实验,搬移 Nand Flash 里的前 4k 代码到内存指定位置,这其实是把
SRAM 从 0x40000000 开始的 4K 代码复制到 SDRAM 的指定位置,并没有涉及到对 Nand
Flash 的操作。究其原因,开发板上电后,Nand Flash 开始的前 4K 数据会被自动复制到
SRAM 0x40000000 开始的 4K 区域里,这个区域被称为 "Steppingstone"。那我们这次就来
操作 Nand Flash,读取它 4K 后的代码到 SDRAM 指定位置,并执行 SDRAM 中的代码。

二、代码
   通过前面做的几个实验,我们已经熟悉了 ARM 开发的基本流程,这可以让我们更关注于
代码的逻辑。好,先来分析文件 head.s:

   @ 文件 head.s
   @ 作用:关闭看门狗、SDRAM 的初始化设置、搬移 Nand Flash 4K 以后
   @ 的代码到 SDRAM 的指定位置、执行 SDRAM 中的代码
   .text
   .global _start
   _start:
    ldr r0, =0x53000000 @ Close Watch Dog Timer
    mov r1, #0x0
    str r1, [r0]
  
    bl  memory_setup  @ Initialize memory setting
    bl  flash_to_sdram @ Copy code to sdram
    
    ldr sp, =0x34000000 @ Set stack pointer
    ldr pc, =main              @ execute the code in SDRAM
  

   @ 文件 mem.s
   @ 作用:SDRAM 的初始化设置
   @ 关于初始化的更多细节,请参考我的前一篇随笔
   .global memory_setup         @ 导出 memory_setup, 使其对链接器可见
   memory_setup:
    mov  r1, #0x48000000
    adrl r2, mem_cfg_val
    add  r3, r1, #13*4
   1: 
    @ write initial values to registers
    ldr  r4, [r2], #4
    str  r4, [r1], #4
    cmp  r1, r3
    bne  1b
    mov  pc, lr
    
    .align 4
   mem_cfg_val:
    .long 0x22111110 @ BWSCON
    .long 0x00000700 @ BANKCON0
    .long 0x00000700 @ BANKCON1
    .long 0x00000700 @ BANKCON2
    .long 0x00000700 @ BANKCON3
    .long 0x00000700 @ BANKCON4
    .long 0x00000700 @ BANKCON5
    .long 0x00018005 @ BANKCON6
    .long 0x00018005 @ BANKCON7 9bit
    .long 0x008e07a3 @ REFRESH
    .long 0x000000b2 @ BANKSIZE
    .long 0x00000030 @ MRSRB6
    .long 0x00000030 @ MRSRB7


   @ 文件 flash.s
   @ 作用:设置 Nand Flash 的控制寄存器、读取 Nand Flash
   @ 中的代码到 SDRAM 的指定位置
   .equ NFCONF, 0x4e000000
   .equ NFCMD,  0x4e000004
   .equ NFADDR, 0x4e000008
   .equ NFDATA, 0x4e00000c
   .equ NFSTAT, 0x4e000010
   .equ NFECC,  0x4e000014
   .global flash_to_sdram
   flash_to_sdram:
        @ Save return addr
        mov r10,lr
    
        @ Initialize Nand Flash
        mov r0,#NFCONF
        ldr r1,=0xf830
        str r1,[r0]
   
        @ First reset and enable Nand Flash
        ldr r1,[r0]
        bic r1, r1, #0x800
        str r1,[r0]
   
        ldr r2,=NFCMD
        mov r3,#0xff
        str r3,[r2]
    
        @ for delay
        mov r3, #0x0a
   1:
        subs r3, r3, #1
        bne 1b
  
   @ Wait until Nand Flash bit0 is 1
   wait_nfstat:
        ldr r2,=NFSTAT
        ldr r3,[r2]
        tst r3,#0x01
        beq wait_nfstat
          
        @ Disable Nand Flash
        ldr r0,=NFCONF
        ldr r1,[r0]
        orr r1,r1,#0x8000
        str r1,[r0]
   
        @ Initialzie stack
        ldr sp,=4096
  
        @ Set arguments and call 
        @ function nand_read defined in nand_read.c
        ldr r0,=0x30000000
        mov r1,#4096
        mov r2,#1024
        bl nand_read
   
        @ return
        mov pc,r10


   /* 文件 nand_read.c
    * 作用:从 Nand Flash 中读取一块数据到 SDRAM 中的指定位置
    */
   #define NFCONF (*(volatile unsigned long *)0x4e000000)
   #define NFCMD  (*(volatile unsigned long *)0x4e000004)
   #define NFADDR (*(volatile unsigned long *)0x4e000008)
   #define NFDATA (*(volatile unsigned long *)0x4e00000c)
   #define NFSTAT (*(volatile unsigned long *)0x4e000010)
   #define NFECC  (*(volatile unsigned long *)0x4e000014)
  
   #define NAND_SECTOR_SIZE 512
   #define NAND_BLOCK_MASK  0x1ff
  
   void wait_idle() {
     int i;
     for (i = 0; i < 50000; ++i) ;
   }
  
   int nand_read(unsigned char *buf, unsigned long start_addr, int size){
     int i, j;
     /*
      * detect the argument
      */ 
     if ((start_addr & NAND_BLOCK_MASK) || (size & NAND_BLOCK_MASK)) {
       return -1;    
     }
   
     /* chip Enable */
     NFCONF &= ~0x800;
     for (i=0; i<10; i++) {
       ;
     }
   
     for (i=start_addr; i < (start_addr + size); i+=NAND_SECTOR_SIZE) {
       NFCMD = 0;
   
       /* Write Address */
       NFADDR = i & 0xff;
       NFADDR = (i >> 9)  & 0xff;
       NFADDR = (i >> 17) & 0xff;
       NFADDR = (i >> 25) & 0xff;
   
       wait_idle();
   
       for(j=0; j < NAND_SECTOR_SIZE; j++) {
         *buf++ = (NFDATA & 0xff);
       }
     }
   
     NFCONF |= 0x800;    /* chip disable */
     return 0;
   }

注:Nand Flash 的设置和读取数据的主要流程简单介绍如下:
   1. NFCONF = 0xf830
   2. 在第一次操作NAND Flash前,通常复位一下:
      NFCONF &= ~0x800 (使能NAND Flash)
      NFCMD = 0xff (reset命令)
      循环查询NFSTAT位0,直到它等于1
   3. NFCMD = 0 (读命令)
   4. 这步得稍微注意一下,请打开K9F1208U0M数据手册第7页,那个表格列出了在地址操
      作的4个步骤对应的地址线,A8没用到:
      NFADDR = addr & 0xff
      NFADDR = (addr>>9) & 0xff (注意了,左移9位,不是8位)
      NFADDR = (addr>>17) & 0xff (左移17位,不是16位)
      NFADDR = (addr>>25) & 0xff (左移25位,不是24位)
   5. 循环查询NFSTAT位0,直到它等于1
   6. 连续读NFDATA寄存器512次,得到一页数据(512字节)
   7. NFCONF |= 0x800 (禁止NAND Flash)


   /* 文件 sdram.c
    * 作用:循环点 FS2410 开发板上的 D9、D10、D11、D12
    * 四个发光二极管。
    */
   #define GPFCON (*(volatile unsigned long *)0x56000050)
   #define GPFDAT (*(volatile unsigned long *)0x56000054)
  
   int main()
   {
     int i,j;
     while(1) {
       for (i = 0; i <4; ++i) {
         GPFCON = 0x1<<(8+i*2);
         GPFDAT = 0x0;
  
         // for delay
         for(j=0;j<50000;++j) ;
       }
     }
   }


   /*
    * 文件 nand.lds (lds 文件是连接脚本)
    */
   SECTIONS {
       first  0x00000000 : { head.o mem.o flash.o nand_read.o }
       second 0x30000000 : AT(4096) { sdram.o }
   }
  
注:这个链接脚本是用来传给链接器的,其作用如下:
  1. 将 head.o 放在 0x00000000 开始的地址处, mem.o、flash.o、and_read.o
     依次放在 head.o 后面, 它们的运行地址是 0x00000000
  2. 将 sdram.o 放在地址 4096 开始处, 但它的运行地址是 0x30000000, 运行前需要
     从 4096 处复制到 SDRAM 的 0x300000000 处

完整的连接脚本文件形式如下:
SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT(ldadr) {contents} >region :phdr =fill
...
}

并非每个选项都是必须的,仅从 nand.lds 用到的来看:
(1) secname: 段名,对于 nand.lds, 段名为 first 和 second
(2) start: 本段的运行时地址,如果没有 AT(xxx),则本段的存储地址也是 start
(3) AT(ldadr): 定义本段存储(加载)的地址
(4) {contents}: 决定哪些内容放在本段,可以是整个目标文件,也可是目标文件中的某段


   # 文件 Makefile
   # 由代码文件生成目标文件,并依据连接脚本 nand.lds 连接目标文件,
   # 最后将连接生成的目标文件转换成二进制格式
   sdram:head.s flash.s mem.s sdram.c
        arm-linux-gcc -c -o head.o head.s
        arm-linux-gcc -c -o mem.o mem.s
        arm-linux-gcc -c -o flash.o flash.s
        arm-linux-gcc -c -o nand_read.o nand_read.c
        arm-linux-gcc -c -o sdram.o sdram.c
        arm-linux-ld  -Tnand.lds head.o mem.o flash.o nand_read.o sdram.o -o sdram_tmp.o
        arm-linux-objcopy -O binary -S sdram_tmp.o sdram
   clean:
        rm -f *.o
        rm -f sdram

三、编译、烧写、测试
Make 一下就会生成我们要的文件 sdram, 将其通过 JTAG 烧入 Nand Flash ,Reset
一下开发板, 呵呵,欣赏我们的成果吧!

posted on 2008-01-06 21:43 Normandy 阅读(4635) 评论(7)  编辑 收藏 引用 所属分类: Embeded Area

评论

# re: 学习 ARM 系列 -- FS2410 开发板上 Nand Flash 到内存的代码搬移  回复  更多评论   

好东西,支持。最好在介绍一下abc.c文件及其作用。
2008-01-06 22:02 | hukq

# re: 学习 ARM 系列 -- FS2410 开发板上 Nand Flash 到内存的代码搬移  回复  更多评论   

LZ 强人呀!学习......
2008-01-07 11:11 | kyle

# re: 学习 ARM 系列 -- FS2410 开发板上 Nand Flash 到内存的代码搬移  回复  更多评论   

请教搂住:
在用SJF2410烧写程序的时候,如果我想把BOOT程序烧写在0000H,MAIN程序烧写在1000H处,而APPLY程序烧写在2000H处,应该如何操作??
一定要烧写三次才可以吗?
谢谢搂主
2008-03-09 14:23 | 葫芦瓜

# re: 学习 ARM 系列 -- FS2410 开发板上 Nand Flash 到内存的代码搬移  回复  更多评论   

@葫芦瓜
有两种思路:
(1) 如果你编译后生成三个二进制程序, 可通过sjf2410 分三次将三个文件烧入 Nand Flash的不同位置, 具体请看 sjf2410.exe 的用法。
(2) 如果你编译后只生成一个二进制文件, 即把 boot 、main 、apply 三个目标文件连接成一个文件,可通过sjf2410 一次烧写即可。要点是在连接时指定连接脚本。 连接脚本如下:

SECTIONS {
first 0x00000000 : { boot.o}
second 0x00000000 : AT(4096) { main.o }
third 0x00000000 : AT(8192) { apply.o }
}

然后在连接程序中这样调用连接脚本:
arm-linux-ld -Tnand.lds boot.o main.o apply.o -o prog_tmp.o
arm-linux-objcopy -O binary -S prog_tmp.o prog


2008-03-10 09:47 | Normandy

# re: 学习 ARM 系列 -- FS2410 开发板上 Nand Flash 到内存的代码搬移  回复  更多评论   

如果从nand boot的话
SRAM是从0-4096
不是从0x40000000开始吧
2008-08-08 01:00 |

# re: 学习 ARM 系列 -- FS2410 开发板上 Nand Flash 到内存的代码搬移[未登录]  回复  更多评论   

@毛
实际上板子加电启动时,SRAM 从 0x40000000 开始的 4096 字节区域被映射成 从 0x00000000 开始的 4096 字节区域, 手册上有说明
2008-08-11 13:06 | Normandy

# re: 学习 ARM 系列 -- FS2410 开发板上 Nand Flash 到内存的代码搬移  回复  更多评论   

我是新手,想问个问题,就是你的这几篇文章的程序可以使用串口或者USB下载到开发板吗?如果行怎么下载,地址是多少,迫切的想知道,其他热心的朋友也可以帮忙回答一下,谢谢!
2008-10-28 16:20 | 李强

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理