• «
  • 1
  • 2
  • »
  • Pages: 1/2     Go
主题 : mini2440 nand flash 操作(C语言版) 复制链接 | 浏览器收藏 | 打印
此生无意行高远,浪迹行书弹指间!
级别: 新手上路
UID: 32708
精华: 2
发帖: 4
金钱: 120 两
威望: 24 点
贡献值: 2 点
综合积分: 48 分
注册时间: 2010-11-19
最后登录: 2011-11-09
楼主  发表于: 2011-11-08 19:40

 mini2440 nand flash 操作(C语言版)

管理提醒: 本帖被 kasim 执行加亮操作(2011-12-11)
         Mini2440 nand flash 操作总结
首先要感谢CSDN论坛的赵春江老师的文章。这篇文章基本上是他文章的内容。
百度上搜 “CSDN 赵春江” 就能找到。

一:硬件连接

    Nand flash操作对于自己编写mini2440启动代非常关键。因为一般来说,嵌入式系统都有一片nand flash用来作为存储器。所以掌握nand flash 的操作非常的必要和关键。
    在操作之前,首先应该对nand flash 的硬件上有一定的了解。我的mini2440板子自带的是K9F1G08U0A。大小是128M。其芯片上的重要引脚如下:
    I/O0~I/O7:数据总线,用于nandflash的所有命令,地址的输入,和数据的双向传输.
    CE :芯片使能引脚
    CLE (Command Latch Enable):命令锁存允许引脚
    ALE(Address Latch Enable):地址锁存允许引脚
    WE/ : 写芯片允许引脚
    RE/ : 读芯片允许引脚
    RB : 状态读取引脚(低电平表示忙;高电平表示空闲)
    其他引脚:诸如电源之类
    
    因为S3C2440硬件自带有nand flash控制器,所以具有相对应的引脚可以和nand flash芯片连接。
    系统总线引脚DATA0~DATA7 :直接与I/O0~I/O7相连接
    nFCE/GPA22  :直接与CE引脚相连
    CLE/GPA17 :直接与CLE引脚相连
    ALE/GPA18 :直接与ALE引脚相连
    nFWE/GPA19 :直接与WE/引脚相连
    nFRE/GPA20 :直接与WE/引脚相连
    FRnB :直接与RB引脚相连
    
    除此之外,S3C2440还有另外几个引脚用于配置nandflash控制器。比如页大小,地址周期数等等。这些引脚主要有如下:
    OM[1:0] ; NCON ; GPG13 ; GPG14 ; GPG15。 他们的配置方式在S3C2440说明书上已经很详细。
    
所以综上所述,S3C2440 与 nandflash(K9F1G08U0A)的硬件连接图如下所示:

  


二:nand flsah 工作原理简介

    Nand flash 主要是用于大容量的存储。它的优点是性价比高,容量大,体积小;缺点是不能随机访问(这是为什么程序不能直接在nandflash中运行的根本原因),容易产生坏块,一般要进行ECC校验。另外nand flash智能把0写成1,不能把1写成0,所以每次往里面写东西,都必须先进行擦除操作。
    Nand flash 的内存分为 “块” 和 “页”。一个页里面有两部分,一部分是Main区,另一部分是spare区。Main区主要用于存放信息数据,spare区主要用来存放ECC校验等。不同的芯片其页的大小不同,块的大小也不同。K9F1G08U0A的页大小为 (2K+64)Byte ,其中64字节就是spare区的大小。那么128M(不包含spare区)的大小,一共就有65536个页。它的一个块包含64个页,所以一共就有1024个块。
    因为该芯片的页大小是2K+64 字节,所以寻址任意一页内的字节需要12根地址总线;
另外,根据芯片总大小(包括spare区)可以算出需要的地址总线一共是 28根。但是为了兼容1G(如果包括spare区,大于1G),地址总线一共是31根。前面提到的12根地址总线表示的是列地址,即寻址的是每一页里面的内容。除了列地址总线,剩余的地址总线称为行地址,用于寻址页。因为nand flash 为了缩小体积,只有8根I/O口,所以为了区分不同的操作,需要事先写入相关的命令来实现。下面是说明书上的一些命令值:

    


    Nand flash 的常用操作一般有
    初始化操作
    复位操作
    读取芯片ID ;读取任意页的整页内容;读取指定页的任意字节;
    写任意页的整页内容;写指定页的任意地址字节

三:S3C2440  nand flash 控制器的相关寄存器介绍

    主要可以分为三类。
第一类:配置、控制寄存器
    NFCONF(配置寄存器):主要用来配置操作时序的长短。
    NFCONT(控制寄存器):第一位用来控制nFCE/GPA22,即芯片的是能引脚。
                           第四位用来复位ECC。
                           第五位main区ECC解锁
                           第六位spare区ECC解锁
第二类:数据传输寄存器
    NFCMMD:用于IO口传送(低8位可用)命令
    NFDATA ,  用于IO口传送(32位可用)数据。注意:因为是32位的,所以对该寄存器进行一次读,硬件会自动对flash进行四次读操作,并把四个字节的内容依次填入NFDAT寄存器。所以最好定义一个该地址的8位寄存器。写入数据也是一样的。
    #define  rNFDATA8  *((char *)0X4E000010)
    NFADDR  用于IO口传送(低8位可用)地址
第三类:ECC校验相关寄存器
    NFMECCD0/1 :nandflash的main区ECC寄存器(用来装载读取到得main区的ECC校
                   验码)
    NFSECCD :   nandflash的spare 区ECC寄存器(用来装载读取到的spare区的ECC
                   校验码)
    NFMECC0/1 :nandflash用于自动保存硬件产生的main区ECC寄存器
    NFSECC   :nandflash用于自动保存硬件产生的spare区的ECC寄存器
第四类:状态寄存器
    NFSTAT :nandflash状态寄存器 。
              第0位可以用于判断nandflash是否在忙。
              第2位对应RnB引脚信号。注意硬件上只能使RnB从低变高,所以每次操
                  作时,都应该先向这位写1,来清除RnB信号。
    NFESTAT0/1:nandflash的ECC状态寄存器
    
    
四:基于S3C2440的nandflash操作函数(C语言版)的编写

    首先宏定义S3C2440的相关寄存器。因为这个工作很简单,这里省略。但是千万要注意下面这个宏的定义
    #define  rNFDATA8  *((char *)0X4E000010)
    
    接着对于nand flash的一些命令进行宏定义
    #define READ_1                 0x00              //页读命令周期1
    #define READ_2                 0x30              //页读命令周期2
    #define READ_ID                0x90              //读ID命令
    #define WRITE_1                0x80              //页写命令周期1
    #define WRITE_2                0x10              //页写命令周期2
    #define ERASE_1                0x60              //块擦除命令周期1
    #define ERASE_2                0xd0              //块擦除命令周期2
    #define READ_STATUS            0x70              //读状态命令
    #define RESET                  0xff              //复位
    #define RANDOM_READ_1          0x05              //随意读命令周期1
    #define RANDOM_READ_2          0xE0              //随意读命令周期2
    #define RANDOM_WRITE           0x85              //随意写命令
    
    下面再宏定义一些简单的操作
    #define ENABLE_NANDFLASH   rNFCONT &= ~(1<<1)        //使能芯片EN
    #define DISABLE_NANDFLASH  rNFCONT |= (1<<1)         //不使能芯片EN
    #define RESET_RNB          rNFSTAT |= (1<<2)         //RnB信号复位(清零)
    #define WAIT_RNB_HIGH      while(!(rNFSTAT&(1<<2)))  //等待RnB信号变高。即
                                                         //等待芯片空闲
    #define ECC_INIT           rNFCONT |= (1<<4)         //复位ECC
    #define MAIN_ECC_UNLOCK    rNFCONT &= ~(1<<5)        //解锁main区的ECC
    #define MAIN_ECC_LOCK      rNFCONT |= (1<<5)         //锁定main区的ECC
    #define SPARE_ECC_UNLOCK   rNFCONT &= ~(1<<6)        //解锁spare区的ECC
    #define SPARE_ECC_LOCK     rNFCONT |= (1<<6)         //锁定spare区的ECC
    
    /*******************************
    **nandflash 初始化函数
    **功能:配置相应的IO引脚
    **设置一些时序相关的时间
    *******************************/
    void NandFlash_Init ( void )
    {
        rGPACON = 0x3f<<17;                     //配置芯片控制引脚
        rNFCONF = (1<<12)|(2<<8)|(0<<4)|(0<<0); //这里的时间配置。要注意和芯片的
                                               //对应性!!!TACLS=1、TWRPH0=2、
                                               //TWRPH1=0,8位IO
    
        rNFCONT=(0<<13)|(0<<12)|(0<<10)|(0<<9)|(0<<8)|(1<<6)|(1<<5)|(1<<4)|(1<<
                 1)|(1<<0);                    
    //非锁定,屏蔽nandflash中断,初
                                                //始化ECC及锁定main区和spare
                                                //区ECC,使能nandflash片选及控
                                                //制器
    }
    
    /*****************************
    **nandflash 的复位函数
    **貌似nandflash使用前
    **必须复位一次
    *****************************/
    void NandFlash_Reset()
    {
          ENABLE_NANDFLASH;       //使能芯片EN
          RESET_RNB;               //RnB信号复位(清零)
          rNFCMD = RESET;         //写入nandflash的复位命令
          WAIT_RNB_HIGH ;         //等待RnB信号变高,即不忙
          DISABLE_NANDFLASH;      //不使能芯片EN
    }
    
    
    /***********************************
    **写入一页函数
    ***********************************/
    unsigend char NandFlash_Write_Page(unsigned int pages)
    {
          unsigned int  i;
          unsigned int  main_ecc, spare_ecc;
          unsigned char stat, temp;
          
            temp = rNF_IsBadBlock(page_number>>6); //判断该块是否为坏块
          if(temp == 0x33)
                 return 0x42;                    //是坏块,返回
    
          ECC_INIT ;                             //复位ECC
          MAIN_ECC_UNLOCK;                       //解锁main区ECC
    
          ENABLE_NANDFLASH;                      //使能芯片EN
          RESET_RNB;                             //RnB信号复位(清零)
    
          rNFCMD=WRITE_1                         //页读命令周期1
    
                                                 //写入5个地址周期
          rNFADDR=(0x00);                         //列地址A0~A7
          rNFADDR=(0x00);                         //列地址A8~A11
          rNFADDR=(pages & 0xff);                 //行地址A12~A19
          rNFADDR=((pages >> 8) & 0xff);          //行地址A20~A27
          rNFADDR=((pages >> 16) & 0xff);         //行地址A28
          
          //写入一页数据
          for (i = 0; i < 2048; i++)
          {
                 rNFDATA8=(INbuffer);         //INbuffer[2048]为全局数组
          }
          
          MAIN_ECC_LOCK;                        //锁定main区的ECC值
          
          main_ecc=rNFMECC0;                     //读取main区的ECC校验码
          //把ECC校验码由字型转换为字节型,并保存到全局变量数组ECCBuf中
          ECCBuf[0]=(U8)(main_ecc&0xff);
          ECCBuf[1]=(U8)((main_ecc>>8) & 0xff);
          ECCBuf[2]=(U8)((main_ecc>>16) & 0xff);
          ECCBuf[3]=(U8)((main_ecc>>24) & 0xff);
    
          SPARE_ECC_UNLOCK;                      //解锁spare区的ECC
          //把main区的ECC值写入到spare区的前4个字节地址内,即第2048~2051地址
          for(i=0;i<4;i++)
          {
                 rNFDATA8=ECCBuf;
            }
    
          SPARE_ECC_LOCK;                      //锁定spare区的ECC值
          spare_ecc=rNFSECC;                  //读取spare区的ECC校验码
          //把ECC校验码保存到全局变量数组ECCBuf中
          ECCBuf[4]=(U8)(spare_ecc&0xff);
          ECCBuf[5]=(U8)((spare_ecc>>8) & 0xff);
          //把spare区的ECC值继续写入到spare区的第2052~2053地址内
          for(i=4;i<6;i++)
          {
                 rNFDATA8=ECCBuf;
          }
    
          rNFCMD=WRITE_2                       //页写命令周期2
    
            delay(1000);                        //延时一段时间,以等待写操作完成
      
        
             rNFCMD=READ_STATUS;                 //读状态命令
      
             //判断状态值的第6位是否为1,即是否在忙,该语句的作用与NF_DETECT_RB();相同
             do{
                stat = NF_RDDATA8();
               }while(!(stat&0x40));
          
        
             DISABLE_NANDFLASH;                   //关闭nandflash片选
        
             //判断状态值的第0位是否为0,为0则写操作正确,否则错误
        if (stat & 0x1)
        {
              temp = rNF_MarkBadBlock(page_number>>6);        //标注该页所在的块为坏块
              if (temp == 0x21)
                     return 0x43           //标注坏块失败
              else
                    return 0x44;         //写操作失败
        }
        else
        return 0x66;               //写操作成功
    }
    

注释:对于写入一页内容思路上不难理解。关键在于ECC校验码部分有点难懂。当向nand flash写入或者读取数据时,s3c2440的nandflash控制器会自动产生ECC校验码,并把它保存在NFMECC0/1和NFSECC寄存器中。当然前提是要解锁main区和spare区的ECC。解锁的意思就是允许硬件产生新的ECC校验码。所以在写一页数据时,解锁main区的ECC校验码,写完之后锁定main区的ECC校验码。然后这一过程由硬件产生的ECC校验码就被保存在了NFMECC0/1寄存器中。然后读取该寄存器的值,将其写入到对应页的spare区的前面四个字节中。当然在写之前要首先解锁spare区的ECC,等把mian区的ECC校验码写完之后,锁定spare区的ECC,然后就可以读取NFSECC寄存器里面产生的spare区的ECC校验码了,再把它写入到spare区的第5,6字节处即可。当然在这一过程中,会涉及到对坏块的判断,这在后面会介绍。





    /**********************************
    **读取芯片ID函数
    **返回一个32位数据,最低8位表示
    **的是设备ID,次低8位是厂商ID
    **********************************/
    unsigned int NandFlash_Read_ID()
    {
          unsigned char FID;      //厂商ID,factory_id
          unsigned char DID;      //设备ID,device_id
          unsigned char ID_detail1,ID_detail2,ID_detail3;  //详细id信息
    
          ENABLE_NANDFLASH;           //使能芯片EN
          RESET_RNB;                    //RnB信号复位(清零)
          rNFCMD = READ_ID;             //读ID命令
          rNFADDR = 0x00;                 //写0x00地址
    
          //读五个周期的ID
          FID = rNFDATA8;                 //厂商ID:0xEC
          DID = rNFDATA8;                //设备ID:0xDA
          ID_detail1 = rNFDATA8;           //更加详细ID信息
          ID_detail1 = rNFDATA8;           //更加详细ID信息
          ID_detail1 = rNFDATA8;           //更加详细ID信息
    
          DISABLE_NANDFLASH;             //不使能芯片EN
          return ((unsined int)FID*0x10000+DIV);    //返回FID和DID
    }
    
    /****************************
    **读取一页内容函数
    **读一页函数
    ****************************/
    unsigned char NandFlash_Read_Page(unsigned int pages)
    {
          unsigned int  i;
          unsigned int  main_ecc, spare_ecc;
    
          ECC_INIT ;                             //复位ECC
          MAIN_ECC_UNLOCK;                       //解锁main区ECC
    
          
          ENABLE_NANDFLASH;                      //使能芯片EN
          RESET_RNB;                             //RnB信号复位(清零)
    
          rNFCMD=READ_1                          //页读命令周期1
    
                                                  //写入5个地址周期
          rNFADDR=(0x00);                        //列地址A0~A7
          rNFADDR=(0x00);                        //列地址A8~A11
          rNFADDR=(pages & 0xff);                 //行地址A12~A19
          rNFADDR=((pages >> 8) & 0xff);          //行地址A20~A27
          rNFADDR=((pages >> 16) & 0xff);         //行地址A28
    
          rNFCMD=READ_2                          //页读命令周期2
    
          WAIT_RNB_HIGH ;                        //等待RnB信号变高,即不忙
          
          //读取一页数据内容
          for (i = 0; i < 2048; i++)             //1页是2KB。所以i取到2048
          {
                 buffer = rNFDATA8;       //注意,这里的buffer【2048】数组是一个全局的数组
          }
          
          MAIN_ECC_LOCK();                    //锁定main区ECC值
          
          SPARE_ECC_UNLOCK();                 //解锁spare区ECC
    
          main_ecc=rNFDATA;                   //读spare区的前4个地址内容,即第2048~2051地址,这4个字节为main区的ECC
                                              //这里的4个字节应当理解成硬件自动完成的。虽然总线只有8跟
          
          //把读取到的main区的ECC校验码放入NFMECCD0/1的相应位置内
          rNFMECCD0=((main_ecc&0xff00)<<8)|(main_ecc&0xff);
          rNFMECCD1=((mecc0&0xff000000)>>8)|((mecc0&0xff0000)>>16);
              
          SPARE_ECC_LOCK();              //锁定spare区的ECC值
    
          spare_ecc=rNFDATA();          //继续读spare区的4个地址内容,即第2052~2055地址,其中前2个字节为spare区的ECC值
          //把读取到的spare区的ECC校验码放入NFSECCD的相应位置内
          rNFSECCD=((spare_ecc&0xff00)<<8)|(spare_ecc&0xff);
    
          DISABLE_NANDFLASH;             //不使能芯片EN
          
          //判断所读取到的数据是否正确
          if ((rNFESTAT0&0xf) == 0x0)
                 return 0x66;                 //正确
           else
                 return 0x44;                 //错误
          
    }
    
这一过程页并不难以理解。还是ECC部分有一点难懂。其思想就是,在读取main区的时候,解锁mian区的ECC,那么读取的过程就会产生mian区的ECC校验码,并自动保存到NFMECC0/1寄存器中。然后再把spare区中的前四个字节(写该页时产生的main区ECC校验码)读取出来,和现在读取的ECC校验码作对比即可。而这个作对比的过程也可以有硬件完成,只要把读取到的ECC校验码装载进NFMECCD0/1寄存器,然后再根据NFESTAT状态寄存器的相应位即可判断。

/***************************************
***在固定页内任意地址读字节函数
***************************************/
unsigned char NANDFLASH_Ramdom_Read(unsigend int pages, unsigend int add)
{
      ENABLE_NANDFLASH;                      //使能芯片EN
      RESET_RNB;                             //RnB信号复位(清零)

      rNFCMD=READ_1                          //页读命令周期1
  
                                            //写入5个地址周期
      NF_ADDR=(0x00);                         //列地址A0~A7
      NF_ADDR=(0x00);                         //列地址A8~A11
      NF_ADDR=(pages & 0xff);                 //行地址A12~A19
      NF_ADDR=((pages >> 8) & 0xff);          //行地址A20~A27
      NF_ADDR=((pages >> 16) & 0xff);         //行地址A28

      rNFCMD=READ_2;                         //页读命令周期2

      WAIT_RNB_HIGH ;                       //等待RnB信号变高,即不忙

      rNFCMD=RANDOM_READ_1                //随意读命令周期1
                                            //页内地址
      rNFADDR=((char)(add&0xff));                        //列地址A0~A7
      rNFADDR=((char)((add>>8)&0x0f));                //列地址A8~A11
      rNFCMD=RANDOM_READ_2;               //随意读命令周期2
      
      return rNFDATA8;                    //读取数据并返回
}

/***************************************
***在固定页内任意地址写字节函数
***************************************/
unsigned char NANDFLASH_Ramdom_Write(unsigned int pages, unsigned int add, unsigned char dat)
{
      unsigned char temp,stat;

      ENABLE_NANDFLASH;                      //使能芯片EN
      RESET_RNB;                             //RnB信号复位(清零)

      rNFCMD=WRITE_1;               //页写命令周期1

                                            //写入5个地址周期
      NF_ADDR=(0x00);                         //列地址A0~A7
      NF_ADDR=(0x00);                         //列地址A8~A11
      NF_ADDR=(pages & 0xff);                 //行地址A12~A19
      NF_ADDR=((pages >> 8) & 0xff);          //行地址A20~A27
      NF_ADDR=((pages >> 16) & 0xff);         //行地址A28

      rNFCMD=RANDOM_WRITE;    ;                //随意写命令
      //页内地址
      NF_ADDR=((char)(add&0xff));                 //列地址A0~A7
      NF_ADDR=((char)((add>>8)&0x0f));         //列地址A8~A11

      rNFDATA8=dat;                            //写入数据

      rNFCMD = WRITE_2;                        //页写命令周期2
      
      delay(1000);                             //延时一段时间
  
        rNFCMD=READ_STATUS;                      //读状态命令
  
      //判断状态值的第6位是否为1,即是否在忙,该语句的作用与NF_DETECT_RB();相同
       do{
          stat = NF_RDDATA8();
       }while(!(stat&0x40));
  
       DISABLE_NANDFLASH;                   //关闭nandflash片选
    
       //判断状态值的第0位是否为0,为0则写操作正确,否则错误
       if (stat & 0x1)
          return 0x44;                //失败
       else
       return 0x66;                //成功
}

//下面介绍上文中提到的判断坏块以及标注坏块的那两个程序:
//rNF_IsBadBlock和rNF_MarkBadBlock。在这里,我们定义在
//spare区的第6个地址(即每页的第2054地址)用来标注坏块,
//0x44表示该块为坏块。要判断坏块时,利用随意读命令来读
//取2054地址的内容是否为0x33,要标注坏块时,利用随意写
//命令来向2054地址写0x33。下面就给出这两个程序,它们的
//输入参数都为块地址,也就是即使仅仅一页出现问题,我们
//也标注整个块为坏块。

unsigned char rNF_IsBadBlock(unsigend int block)
{
      return NANDFLASH_Ramdom_Read(block*64, 2054);
}

unsigend char rNF_MarkBadBlock(unsigend int block)
{
     unsigend char result;
   result = NANDFLASH_Ramdom_Write(block*64, 2054, 0x33);
   if(result == 0x44)
     return 0x21;                 //写坏块标注失败
      else
   return 0x60;                 //写坏块标注成功
}
    
                                                                         中国计量学院  郑星
                                                                         newstar111@163.com
级别: 骑士
UID: 14419
精华: 1
发帖: 185
金钱: 995 两
威望: 199 点
贡献值: 1 点
综合积分: 390 分
注册时间: 2010-02-08
最后登录: 2014-04-13
1楼  发表于: 2011-11-08 20:21
沙发!!有图就更好了。。。
级别: 新手上路
UID: 52806
精华: 0
发帖: 4
金钱: 20 两
威望: 4 点
贡献值: 0 点
综合积分: 8 分
注册时间: 2011-07-25
最后登录: 2012-09-23
2楼  发表于: 2011-12-16 21:24
推推
级别: 新手上路
UID: 61185
精华: 0
发帖: 22
金钱: 110 两
威望: 22 点
贡献值: 0 点
综合积分: 44 分
注册时间: 2011-12-24
最后登录: 2012-12-15
3楼  发表于: 2011-12-29 13:32
顶起来
级别: 新手上路
UID: 68257
精华: 0
发帖: 2
金钱: 10 两
威望: 2 点
贡献值: 0 点
综合积分: 4 分
注册时间: 2012-04-20
最后登录: 2012-04-20
4楼  发表于: 2012-04-20 16:03
刚好需要
级别: 新手上路
UID: 83336
精华: 0
发帖: 16
金钱: 80 两
威望: 16 点
贡献值: 0 点
综合积分: 32 分
注册时间: 2012-12-05
最后登录: 2017-09-13
5楼  发表于: 2012-12-06 10:13
非常感谢!!!
级别: 新手上路
UID: 85337
精华: 0
发帖: 2
金钱: 10 两
威望: 2 点
贡献值: 0 点
综合积分: 4 分
注册时间: 2013-01-04
最后登录: 2013-01-07
6楼  发表于: 2013-01-04 11:45
非常感谢!!!
级别: 禁止发言
UID: 133549
精华: 0
发帖: 2701
金钱: 13505 两
威望: 2701 点
贡献值: 0 点
综合积分: 5402 分
注册时间: 2017-07-06
最后登录: 2017-07-08
7楼  发表于: 2017-07-06 22:11
用户被禁言,该主题自动屏蔽!
级别: 禁止发言
UID: 133549
精华: 0
发帖: 2701
金钱: 13505 两
威望: 2701 点
贡献值: 0 点
综合积分: 5402 分
注册时间: 2017-07-06
最后登录: 2017-07-08
8楼  发表于: 2017-07-07 02:27
用户被禁言,该主题自动屏蔽!
级别: 风云使者
UID: 133554
精华: 0
发帖: 2702
金钱: 13510 两
威望: 2702 点
贡献值: 0 点
综合积分: 5404 分
注册时间: 2017-07-06
最后登录: 2017-09-30
9楼  发表于: 2017-07-07 08:34
呵呵,支持一下哈
  • «
  • 1
  • 2
  • »
  • Pages: 1/2     Go