打印
[ZLG-ARM]

请教如何使用单片机读写CF卡

[复制链接]
10129|9
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
lgfsh|  楼主 | 2009-4-18 20:54 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
沙发
lgfsh|  楼主 | 2009-4-19 08:56 | 只看该作者

没人光顾啊~~~~

怎么没人啊??

使用特权

评论回复
板凳
initer| | 2009-4-20 10:30 | 只看该作者

按照SD卡的规范就可以啦

使用特权

评论回复
地板
碧波仙子| | 2009-4-20 13:12 | 只看该作者

楼上错了

CF卡采用的类似硬盘并行的ATA接口方法来读取数据
SD卡采用的SPI串口的方法

使用特权

评论回复
5
qingfengyishi| | 2009-4-20 13:23 | 只看该作者

lgfsh,ZLG的EasyARM开发板带的程序中有示例代码

lgfsh,ZLG的EasyARM开发板带的程序中有示例代码,可以参考一下

使用特权

评论回复
6
lgfsh|  楼主 | 2009-4-23 10:25 | 只看该作者

感谢楼上的各位兄弟

感谢楼上的各位兄弟!
特别是qingfengyishi!
可是我手头没有EasyARM的开发板,谁能给我发一下这个示例代码啊?万分感谢!我的邮件是lgfsh@yahoo.com.cn

使用特权

评论回复
7
armecos| | 2009-4-23 11:01 | 只看该作者

EASYARM2200开发板的资料最多了,

根本不用自己开发,全是现成的,www.armecos.com上有免费测试程序,直接可以插卡使用。


********************
* CF卡/HDD硬盘驱动 *
********************
    2007/07/30 asdjf@163.com www.armecos.com
    
    CF卡驱动(兼容硬盘驱动)是相当简单的,主要内容就是读写扇区,也就是数据块,我们称之为块设备驱动。基于分层思想,块驱动部分不考虑数据格式,只负责实现机制,具体的数据解释交由上层文件系统负责,这样一来,就没有什么太多内容了,分层处理就是好啊!块设备具有移动特性,支持热插拔,所以,就存在一个在位探测和身份识别问题。当然,驱动还必须符合ecos要求。
    
    《ecos增值包》提供了大量支撑软件模块,使得初学者能把精力集中在CF卡驱动上,而不必在其他细节上浪费时间。例如:ecos库负责初始化硬件;不必自己编写串口操作,需要输出信息时只要printf即可;精确的延时函数,使用定时器实现而不是多重循环,ecos负责复杂的计算和烦琐的定时器操作。
    
    下面详细介绍CF卡驱动编写。
    
    -----------------
    端口输入/输出操作
    -----------------
    CF卡驱动中最基本的操作就是端口输入/输出,用于访问地址映射的寄存器,数据位宽8位/16位,满足ATA接口/总线时序。

#define HAL_IDE_READ_UINT8(ctrl, reg, val) 
                              do{ 
                                HAL_WRITE_UINT16(ATA_EN|ATA_ALE|reg, 0); 
                                HAL_READ_UINT16(ATA_EN|ATA_RW, val); 
                            HAL_WRITE_UINT16(ATA_EN|ATA_ALE|ATA_OUT|ATA_CS1|ATA_CS0, 0);
                                }while(0)
                                
#define HAL_IDE_WRITE_UINT8(ctrl, reg, val) 
                              do{ 
                                HAL_WRITE_UINT16(ATA_EN|ATA_ALE|ATA_OUT|reg, 0); 
                            HAL_WRITE_UINT16(ATA_EN|ATA_RW, val); 
                            HAL_WRITE_UINT16(ATA_EN|ATA_ALE|ATA_OUT|ATA_CS1|ATA_CS0, 0);
                                }while(0)

#define HAL_IDE_READ_UINT16(ctrl, reg, val) 
                              do{ 
                                HAL_WRITE_UINT16(ATA_EN|ATA_ALE|reg, 0); 
                                HAL_READ_UINT16(ATA_EN|ATA_RW, val); 
                                HAL_WRITE_UINT16(ATA_EN|ATA_ALE|ATA_OUT|ATA_CS1|ATA_CS0, 0); 
                                }while(0)
                                
#define HAL_IDE_WRITE_UINT16(ctrl, reg, val) 
                              do{ 
                                HAL_WRITE_UINT16(ATA_EN|ATA_ALE|ATA_OUT|reg, 0); 
                                HAL_WRITE_UINT16(ATA_EN|ATA_RW, val); 
                                HAL_WRITE_UINT16(ATA_EN|ATA_ALE|ATA_OUT|ATA_CS1|ATA_CS0, 0); 
                                }while(0)
    
    
    参数ctrl用于区分IDE控制器,有些设备支持2个IDE口,每个口支持主/从两块硬盘,控制器挂在PCI总线上,驱动程序需要知道控制器号和主从选择才能具体定位一块硬盘。

    reg是寄存器地址。
    
// IDE Register Indices
#define IDE_REG_DATA      0x20  //0
#define IDE_REG_ERROR     0x22  //1
#define IDE_REG_FEATURES  0x22  //1
#define IDE_REG_COUNT     0x24  //2
#define IDE_REG_REASON    0x24  //2  // ATAPI
#define IDE_REG_LBALOW    0x26  //3
#define IDE_REG_LBAMID    0x28  //4
#define IDE_REG_LBAHI     0x2A  //5
#define IDE_REG_DEVICE    0x2C  //6
#define IDE_REG_STATUS    0x2E  //7
#define IDE_REG_COMMAND   0x2E  //7

    注意:板子的A1接IDE的A0,还有CS0/CS1也映射到地址里了,所以右边注释掉的是手册给出的值,真正用到的是修正后的地址值。
    
    数据位宽支持8位/16位,所以分别提供读写接口。
    
    do...while(0)是用于宏定义的一种方法,它把若干条语句合成一条语句。注意这里用的是宏,这4条语句频繁调用,严重影响效率,而且不大,所以用宏替换而不是语句,这样可以提高效率又不会造成太大的存储空间浪费。
    
    因为开发板是用扩展电路模拟ATA时序,没有直接连接MCU和ATA总线,所以,需要用3条语句实现读写功能,略显烦琐。如果硬件支持,用单条语句实现甚好。
    
    注意函数命名规则符合ecos规范,例如:HAL_IDE_READ_UINT8。HAL说明这是一个硬件抽象层函数,与硬件相关,IDE说明与IDE硬件设备相关,READ表明是个读操作,UINT8表示位宽为8bit,无符号整数类型。这样的写法含义一目了然,又不会重名,造成名字污染,更不会让我们费脑子琢磨如何起名字。类似这样的细节在《ecos增值包》里还有很多,就不一一指明了,多思考多体会就能从中学到不少东西。
    
    --------
    等待操作
    --------
    CF卡/硬盘都是慢速设备,MCU发出请求后不能立即响应,需要等待。所谓等待就是判断状态位(有些支持超时退出)。
    
static inline void
__wait_for_ready(int ctlr)
{
    cyg_uint8 status;
    do {
         HAL_IDE_READ_UINT8(ctlr, IDE_REG_STATUS, status);
    } while (status & (IDE_STAT_BSY | IDE_STAT_DRQ));
}

// Wait while the device is busy with the last command
static inline int
__wait_busy(int ctlr)
{
    cyg_uint8 status;
    cyg_ucount32 tries;
    
    for (tries=0; tries < 1000000; tries++) {
         CYGACC_CALL_IF_DELAY_US(10);
         HAL_IDE_READ_UINT8(ctlr, IDE_REG_STATUS, status);
         if ((status & IDE_STAT_BSY) == 0)
              return 1;
    }
    return 0;   
}

static inline int
__wait_for_drq(int ctlr)
{
    cyg_uint8 status;
    cyg_ucount32 tries;

    for (tries=0; tries<1000000; tries++) {
        CYGACC_CALL_IF_DELAY_US(10);
        HAL_IDE_READ_UINT8(ctlr, IDE_REG_STATUS, status);
        if (!(status & IDE_STAT_BSY)) {
            if (status & IDE_STAT_DRQ)
                return 1;
            else
                return 0;
        }
    }
    return 0;
}

    注意这些函数前面都有inline关键字,就是说尽可能替换而不要调用函数。因为这些等待函数会被频繁调用,所以用替换可以提高效率。
    
    状态寄存器里提供多种状态指示,用得比较多的是BSY和DRQ。
    
#define IDE_STAT_BSY      0x80
#define IDE_STAT_DRDY     0x40
#define IDE_STAT_SERVICE  0x10
#define IDE_STAT_DRQ      0x08
#define IDE_STAT_CORR     0x04
#define IDE_STAT_ERR      0x01

    延时函数使用ecos提供的CYGACC_CALL_IF_DELAY_US,它是用定时器实现的微秒级延时,非常精确,可移植(多重循环在不同频率机器上需要重新计算,而且由于多级流水线等加速措施导致不能精确计算耗时)。用户只要填写想要延时的微秒数即可。注意:受限于定时器计数器位宽,太大的延时可能溢出,这个函数适于短延时。
    
    --------
    在位判断
    --------
    块设备可能被移动,所以需要随时检测是否在位。方法是向设备/磁头寄存器里写入数据再读出,若一致即表明设备在位,否则说明没有设备存在。这个寄存器可读写并保存数据,所以就不必使用额外的GPIO探测线了。只要主从有一个在位就返回确认,如果是在VMWARE里仿真,就固定使用虚拟的主设备。我只在开发板上接一个CF卡,所以这个函数能正常工作。
    
static int
ide_presence_detect(int ctlr)
{
    cyg_uint8 sel, val;
    int i;

    for (i = 0; i < 2; i++) {
        sel = (i << 4) | 0xA0;
        CYGACC_CALL_IF_DELAY_US((cyg_uint32)50000);
        HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_DEVICE, sel);
        CYGACC_CALL_IF_DELAY_US((cyg_uint32)50000);
        HAL_IDE_READ_UINT8(ctlr, IDE_REG_DEVICE, val);
        if (val == sel) {
#ifndef CYGSEM_DEVS_DISK_IDE_VMWARE
            if (i)
                HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_DEVICE, 0);
#endif
            return 1;
        }
    }
    return 0;
}

    --------
    复位操作
    --------
    硬盘初始工作前要先进入一个确定状态,方法是拉低ATA_RST引脚一段时间,然后置高并判断状态是否就绪。
    
static int
ide_reset(int ctlr)
{
    cyg_uint8 status;
    int delay, i;
    int tmp;

    HAL_READ_UINT32(LPC2XXX_GPIO_IO2DIR, tmp);
    tmp |= ATA_RST;
    tmp &= (~ATA_CD);  //可不用
    HAL_WRITE_UINT32(LPC2XXX_GPIO_IO2DIR, tmp);
      
    HAL_WRITE_UINT32(LPC2XXX_GPIO_IO2CLR, ATA_RST);
    cyg_thread_delay(1);
    HAL_WRITE_UINT32(LPC2XXX_GPIO_IO2SET, ATA_RST);
        
    printf("IDE Reset! ");
        
    // wait 30 seconds max for not busy and drive ready
    for (delay = 0; delay < 300; ++delay) {
        for(i = 0; i < 100; i++){
        CYGACC_CALL_IF_DELAY_US((cyg_uint32)1000);  //yynote 延时不能太长,否则微秒延时函数无法正常工作
        HAL_IDE_READ_UINT8(ctlr, IDE_REG_STATUS, status);
            if (!(status & IDE_STAT_BSY)) {
                if (status & IDE_STAT_DRDY) {
                    return 1;
                }
            }
        }
    }
    return 0;
}

    --------
    设备识别
    --------
    每个卡或硬盘都有自己的参数信息,通过ECH命令可以读出来,其格式如ide_identify_data_t结构体所示。我们可以通过这些信息了解设备情况以及确定是否能够操作它。当然,CF驱动只管读出数据存放到buf里,它不关心这个数据结构的。cyg_uint16表示cygnus公司定义的16位无符号整数,用它定义数据类型的程序移植性好。注意数据都是通过16位数据总线读出来的,命令才用8位的读写函数,这样数据吞吐量大。
    
typedef struct ide_identify_data_t_ {        
    cyg_uint16 general_conf;         // 00    : general configuration   
    cyg_uint16 num_cylinders;        // 01    : number of cylinders (default CHS trans) 
    cyg_uint16 reserved0;            // 02    : reserved 
    cyg_uint16 num_heads;            // 03    : number of heads (default CHS trans) 
    cyg_uint16 num_ub_per_track;     // 04    : number of unformatted bytes per track 
    cyg_uint16 num_ub_per_sector;    // 05    : number of unformatted bytes per sector 
    cyg_uint16 num_sectors;          // 06    : number of sectors per track (default CHS trans) 
    cyg_uint16 num_card_sectors[2];  // 07-08 : number of sectors per card 
    cyg_uint16 reserved1;            // 09    : reserved 
    cyg_uint16 serial[10];           // 10-19 : serial number (string) 
    cyg_uint16 buffer_type;          // 20    : buffer type (dual ported) 
    cyg_uint16 buffer_size;          // 21    : buffer size in 512 increments 
    cyg_uint16 num_ECC_bytes;        // 22    : number of ECC bytes passed on R/W Long cmds 
    cyg_uint16 firmware_rev[4];      // 23-26 : firmware revision (string)
    cyg_uint16 model_num[20];        // 27-46 : model number (string)
    cyg_uint16 rw_mult_support;      // 47    : max number of sectors on R/W multiple cmds
    cyg_uint16 reserved2;            // 48    : reserved 
    cyg_uint16 capabilities;         // 49    : LBA, DMA, IORDY support indicator 
    cyg_uint16 reserved3;            // 50    : reserved 
    cyg_uint16 pio_xferc_timing;     // 51    : PIO data transfer cycle timing mode 
    cyg_uint16 dma_xferc_timing;     // 52    : single word DMA data transfer cycle timing mode 
    cyg_uint16 cur_field_validity;   // 53    : words 54-58 validity (0 == not valid) 
    cyg_uint16 cur_cylinders;        // 54    : number of current cylinders 
    cyg_uint16 cur_heads;            // 55    : number of current heads 
    cyg_uint16 cur_spt;              // 56    : number of current sectors per track 
    cyg_uint16 cur_capacity[2];      // 57-58 : current capacity in sectors 
    cyg_uint16 mult_sectors;         // 59    : multiple sector setting 
    cyg_uint16 lba_total_sectors[2]; // 60-61 : total sectors in LBA mode 
    cyg_uint16 sw_dma;               // 62    : single word DMA support 
    cyg_uint16 mw_dma;               // 63    : multi word DMA support 
    cyg_uint16 apio_modes;           // 64    : advanced PIO transfer mode supported 
    cyg_uint16 min_dma_timing;       // 65    : minimum multiword DMA transfer cycle 
    cyg_uint16 rec_dma_timing;       // 66    : recommended multiword DMA cycle 
    cyg_uint16 min_pio_timing;       // 67    : min PIO transfer time without flow control 
    cyg_uint16 min_pio_iordy_timing; // 68    : min PIO transfer time with IORDY flow control 
//  cyg_uint16 reserved4[187];       // 69-255: reserved 
} ide_identify_data_t;

static int
ide_ident(int ctlr, int dev, cyg_uint16 *buf)
{
    int i;

    if (!__wait_busy(ctlr)) {
         return 0;
    }
    
    HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_DEVICE, dev << 4);
    HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_COMMAND, 0xEC);
    CYGACC_CALL_IF_DELAY_US((cyg_uint32)50000);

    if (!__wait_for_drq(ctlr)) {
         return 0;
    }
    
    for (i = 0; i < (CYGDAT_DEVS_DISK_IDE_SECTOR_SIZE / sizeof(cyg_uint16));
         i++, buf++)
        HAL_IDE_READ_UINT16(ctlr, IDE_REG_DATA, *buf);

    return 1;
}

    --------
    读写扇区
    --------
    最主要的工作内容,最好基于DMA和中断操作,本板采用普通操作。写和读类似,略。
    
static int
ide_read_sector(int ctlr, int dev, cyg_uint32 start, 
                cyg_uint8 *buf, cyg_uint32 len)
{
    int j, c;
    cyg_uint16 p;
    cyg_uint8 * b=buf;

    if(!__wait_busy(ctlr)) {
         return 0;
    }
    
    HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_COUNT, 1);    // count =1
    HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_LBALOW, start & 0xff);
    HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_LBAMID, (start >>  8) & 0xff);
    HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_LBAHI,  (start >> 16) & 0xff);
    HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_DEVICE,
          ((start >> 24) & 0xf) | (dev << 4) | 0x40);
    HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_COMMAND, 0x20);

    if (!__wait_for_drq(ctlr))
        return 0;
 
    for (j = 0, c=0 ; j < (CYGDAT_DEVS_DISK_IDE_SECTOR_SIZE / sizeof(cyg_uint16));
         j++) {
        HAL_IDE_READ_UINT16(ctlr, IDE_REG_DATA, p);
        if (c++<len) *b++=p&0xff;
        if (c++<len) *b++=(p>>8)&0xff;
    }
    return 1;
}

    ctrl是控制器号,dev是主从选择,start是扇区号,buf是数据缓冲区,len 是操作长度。
    使用下面语句可以读出指定硬盘上的指定扇区数据到buf里。示例:读第一个IDE口的主硬盘的0扇区。
    
    if(!ide_read_sector(0, 0, 0, buf, 512)){
            printf("ide_read_sector ERROR! ");
    }
    
    --------------
    可视化设备信息
    --------------
    设备识别信息的可视化。

static void 
ide_disk_info(void)
{
    cyg_uint32 id_buf[CYGDAT_DEVS_DISK_IDE_SECTOR_SIZE/sizeof(cyg_uint32)];
    ide_identify_data_t *ide_idData=(ide_identify_data_t*)id_buf;
    char buf[50];
        
    if (!ide_ident(0, 0, (cyg_uint16 *)id_buf)) {
        printf("IDE ident DRQ error ");
    }

    id_strcpy(buf, ide_idData->serial, 20);
    printf(" Serial : %s ", buf);
    id_strcpy(buf, ide_idData->firmware_rev, 8);
    printf(" Firmware rev. : %s ", buf);
    id_strcpy(buf, ide_idData->model_num, 40);
    printf(" Model : %s ", buf);
    printf(" C/H/S : %d/%d/%d ", ide_idData->num_cylinders, 
                              ide_idData->num_heads, ide_idData->num_sectors);
    printf(" LBA sector number: %d ", (ide_idData->lba_total_sectors[1] << 16 | ide_idData->lba_total_sectors[0]));
    printf(" Kind : %x ", (ide_idData->general_conf>>8)&0x1f);

}
    
    通过以上这些程序就可以随意操作CF卡了,完整程序在光盘里,用任何一个库编译都可以,推荐lpc_default_ram_install,这个库体积小,在RAM里运行,配合redboot,调试起来很方便。注意JP7、JP11跳线要设置正确。

使用特权

评论回复
8
armecos| | 2009-4-23 11:07 | 只看该作者

文件系统也是现成的,你只要专注在采集方面即可,

在采集方面提供多种现成总线驱动,GPIO操作等等。

*********************************
* 《ecos增值包》之FAT文件系统篇 *
*********************************
             ---FAT12/16/32在SD/MMC/CF/HDD等介质上的实现
        2007/10/28 asdjf@163.com www.armecos.com

    很多网友对实现FAT文件系统感兴趣,经常有人来信询问设计思路或者索要源码,其实,使用《ecos增值包》是不需要理会这些细节的,直接使用UNIX文件系统API接口就能在各种介质上完成FAT文件访问,不过,我们仍然愿意为《ecos增值包》正式用户提供这个设计思路文档。
    
    《ecos增值包》支持FAT12/16/32,标准UNIX文件系统接口,各种容量的SD/MMC/CF/HDD等介质,充分考虑到了速度、效率、容量、低功耗、简单性、安全性、可靠性、扩展性、并发访问、存储介质多样等各种性能指标参数。
    
    下面我们分四个层次逐步讲解FAT文件系统的设计思路。注意:《ecos增值包》不仅仅只支持FAT文件系统,它还支持RAMFS/ROMFS/JFFS2/Yaffs/TrueFFS等,所以下面介绍的内容是高度抽象的,不仅仅只适合于FAT文件系统。
    
    1、首先,我们介绍块设备驱动的特点和实现,抽象出SD/MMC/CF/HDD等块设备共有的特性,设计出适合现在及未来任何介质的万能块设备驱动,进而实现FAT over anything。
    
    2、接着讨论高速块设备驱动,这是体现块设备性能的关键指标。设计思路很简单,就是提供一个cache,读写数据尽量在高速cache中完成,必要时才读写真正的慢速块设备。这里用到了链表、HASH映射、排序二叉树等复杂的数据结构。
    
    3、有了高速的块设备驱动,文件系统的性能就有了物质保障,但是恰当地选择下层和上层数据结构,能进一步将性能发挥到极致。这里给出了块设备、磁盘、目录、节点等的数据结构描述。
    
    4、准备好了FAT相关的数据结构,接着就可以提供操作这些数据结构的函数,当然这是完全符合UNIX文件系统API接口的函数集合。用户可以使用简单统一的mount/open/read/write/close等API函数操作各种块设备。
    
    最后,我们说明UNIX相对DOS文件系统的优势并举例说明UNIX文件系统的使用方法。
    
    ----------
    块设备驱动
    ----------
    
    SD卡、MMC卡、CF卡、硬盘等大容量存储设备虽然介质形态不同,但都可以抽象出共同的特征。一般,此类设备以数据块方式被访问,工作过程中可能被移动改变。比如:动态插入、移除、改变设备,这就要求OS能够检测出设备是否在位,是否被删除,是否被更换成另一个设备。此外,有些设备提供了电源开关,在不需要使用时可以关闭电源以节省能量。有些设备涉及机械运动,如:硬盘磁头移动,这时采用一些软件算法可以大大提高性能,如:电梯排队算法,聚集操作等。还有,通过DMA + 中断等硬件加速方式可以进一步提高吞吐效率。ecos比Linux设备驱动强在没有复杂I/O结构,不涉及特权模式,可以直接操作设备,这样代码大小和读写效率都比较好。
    
    经过抽象,所有块设备驱动都可以总结为下面几种操作:读read、写write、设备自身属性操作ioctl(如弹出/关闭光盘托盘等)、初始化init、查找/枚举设备lookup。不同的设备操作方法不尽相同,如SD卡通过SPI总线访问设备而CF卡通过ATA接口访问,但是经过驱动程序抽象后,上层软件将看到统一的设备接口,所以不论硬盘、光盘、SD卡、CF卡,经过设备驱动抽象后对高层软件来说都是一样的。
    
    SD/MMC/CF/HDD/CDROM/DVDROM等介质的设备驱动详见各相关部分的描述,下面给出一个IDE硬盘接口函数的例子(其他介质与此类似,函数内部操作替换成特定介质要求的操作,对外接口不变,都是下面这个样子):

static cyg_bool ide_disk_init(struct cyg_devtab_entry *tab);

static Cyg_ErrNo ide_disk_read(disk_channel *chan, void *buf, cyg_uint32 len, cyg_uint32 block_num);

static Cyg_ErrNo ide_disk_write(disk_channel *chan, const void *buf, cyg_uint32 len, cyg_uint32 block_num);

static Cyg_ErrNo ide_disk_get_config(disk_channel *chan, cyg_uint32 key, const void *xbuf, cyg_uint32 *len);

static Cyg_ErrNo ide_disk_set_config(disk_channel *chan, cyg_uint32 key, const void *xbuf, cyg_uint32 *len);

static Cyg_ErrNo ide_disk_lookup(struct cyg_devtab_entry **tab, struct cyg_devtab_entry *sub_tab, const char *name);

    --------------
    高速块设备驱动
    --------------
    
    仅有块设备驱动还是远远不够的,因为这些大容量存储设备相对CPU而言都是慢速设备,如果不采取任何措施直接访问,那么效果会非常不令人满意。更何况现代的虚拟存储系统会把内存中暂时不需要的数据转移到硬盘上,从而模拟出一个比实际大得多的内存,所以,块设备驱动不仅与文件系统性能有关,还和OS虚拟内存性能有关。如何实现高速大容量数据访问呢?一个非常有效的办法就是通过内存提供一块CACHE暂存数据,读写数据尽量在高速cache中完成,必要时才读写真正的慢速大容量块设备。这种方法存在一定的风险,如果在数据真正写入设备前掉电,那么cache缓存里的数据将丢失。
    
    上面笼统地说到要缓存数据,但是何时缓存,怎么缓存,切入点在什么地方并没有说清楚。其实具体到FAT文件系统里,就是要缓存FAT表数据、文件目录和文件数据。其他一些数据,如引导信息等在设备枚举初始化后就被存到相应的设备信息结构体里了,以后再访问这些数据直接从这些结构体里提取即可,不需要每次都重新读设备。
    
    先说说FAT表是如何缓存的吧。一般来说FAT表大小不固定,FAT表数量也可以有多个(用于冗余备份,提高安全性),而且操作频繁,属于关键数据,但是如果一次将整个FAT表缓存,首先不知道整个FAT表有多大,数量有多少,即使知道这些,内存可能也装不下整个FAT表,况且只有少部分FAT信息被用到,那么怎么缓存FAT表才是最佳的呢?办法就是只缓存用到的一小块FAT信息,把整个FAT表打散成一个个小碎片,用到哪块就缓存哪块。问题又来了,你是如何知道某个小碎片对应哪个FAT表片段啊?如果内存不够用了该怎么办啊?
    
    呵呵,这就要用到一系列的复杂数据结构了。FAT表里使用“簇”来描述信息,而内存中使用内存块来表述,所以,首先要写一个“簇号<-->块号”的互换函数,使得FAT信息和内存块对应起来。然后把缓存的FAT碎片用链表连接起来,以后每次读写FAT信息时,先在这个链表里查找该FAT信息是否已经被缓存,判断方法是把簇号转换成块号,在链表里搜索有无相同块号的内存块,如果有相同块号的内存块,就说明此FAT片段先前已经被缓存。与此同时,把该块内存块移动到链表头部,根据临近相关原理,下次被搜索的概率最大,因为离头部最近,所以可以提高搜索效率和命中率。如果内存不够用了,那么首先释放链表末尾的数据块,因为那个位置的内存块意味着最长时间没有被访问过。如果事先将各个内存块连成排序二叉树,那么查找速度还会提升。任何写入的FAT信息将被同时写入多个FAT表里。
    
    说完FAT表这个最关键的数据缓存,接着就要说说文件目录的缓存。和通过簇号信息访问FAT表不同,目录是通过路径名访问的,那么如何通过路径名得知目录是否被缓存呢?ecos文件系统和UNIX文件系统一样,把文件和目录抽象成节点node,在node里保存相关信息。从文件名映射到节点的最好办法就是HASH映射。

static unsigned int 
hash_fn(const char *str, unsigned int strlen)
{
    unsigned int i = 0, val = 0;

    while (i++ < strlen)
        val = (val ^ (int)toupper(*str++)) << 1;
    return(val);
}

    通过上面提供的HASH算法,给出文件路径名和其长度,立即可以得到节点号。如:node = hash_fn(filename, filenamelen)。HASH算法可能产生碰撞,此时需进一步调整,不过总体而言,映射效率很高。文件名比较时先比较长度再比较文件名字串,这样速度快。通过node_hash_find()函数可以立即判断目录是否已在缓存里。
    
    最后说下文件数据缓存。FAT文件对应簇号,只要根据簇号找到内存块就可以找到数据缓存,和FAT表的缓存原理一样。
    
    读数据时,先判断cache缓存里是否已存在,如果存在就直接读取数据,否则,从真实设备中读取数据并缓存;
    写数据时,直接写入cache中,没有就创建一个内存缓存块,当释放缓存时,数据写入实际设备中。只要在缓存链表里的数据肯定都是“脏”的。
    
    通过缓存FAT表片段、文件目录项、文件数据,大容量慢速存储设备就被虚拟成了一个高速的块设备。
    
    -----------------------
    FAT文件系统相关数据结构
    -----------------------
    
    有了高速的块设备驱动,接下来就要在上面实现FAT文件系统,这时,看到的块设备就是统一的FAT格式,FAT文件系统的本意也就是在块设备上以FAT格式组织文件信息。FAT文件系统的操作对象就是一堆数据结构。
    
    首先介绍一个宏定义:DISK_INSTANCE(_number_,_port_,_chan_,_mbr_supp_,_name_)
    不同的介质支持的FAT结构略有不同,如软盘和CF卡不支持MBR而SD卡和硬盘就支持。有些设备格式化为几个逻辑分区,每个分区都要用一个名字对应。这个宏就是用来通知OS介质支持的具体FAT格式的。如:

DISK_INSTANCE(0, 0, 0, true, "/dev/hda/");
DISK_INSTANCE(1, 0, 1, true, "/dev/hdb/");
DISK_INSTANCE(2, 1, 0, true, "/dev/hdc/");
DISK_INSTANCE(3, 1, 1, true, "/dev/hdd/");

    就表示支持4个硬盘,IDE0的主从,IDE1的主从,均支持MBR,其设备名后缀依次为a、b、c、d。
    
    文件目录项结构体
typedef struct fatfs_dir_entry_s
{
    char              filename[12+1]; // File name
    mode_t            mode;           // Node type
    size_t            size;           // Size of file in bytes
    time_t            ctime;          // Creation timestamp
    time_t            atime;          // Last access timestamp
    time_t            mtime;          // Last write timestamp
    cyg_uint8         priv_data;      // Private data
    cyg_uint32        cluster;        // First cluster number
    cyg_uint32        parent_cluster; // First cluster of parent dentry
    fatfs_data_pos_t  disk_pos;       // Position of dir entry on disk
#ifdef CYGCFG_FS_FAT_USE_ATTRIBUTES
    cyg_fs_attrib_t    attrib;     // Attribute bits for DOS compatability
#endif //CYGCFG_FS_FAT_USE_ATTRIBUTES
} fatfs_dir_entry_t;


    存储设备全局结构体
typedef struct fatfs_disk_s
{
    cyg_uint32    sector_size;          // Sector size in bytes
    cyg_uint32    sector_size_log2;     // Sector size log2
    cyg_uint32    cluster_size;         // Cluster size in bytes
    cyg_uint32    cluster_size_log2;    // Cluster size log2 
    cyg_uint32    fat_tbl_pos;          // Position of the first FAT table
    cyg_uint32    fat_tbl_size;         // FAT table size in bytes
    cyg_uint32    fat_tbl_nents;        // Number of entries in FAT table
    cyg_uint32    fat_tbls_num;         // Number of FAT tables
    cyg_uint32    fat_root_dir_pos;     // Position of the root dir
    cyg_uint32    fat_root_dir_size;    // Root dir size in bytes 
    cyg_uint32    fat_root_dir_nents;   // Max number of entries in root dir
    cyg_uint32    fat_root_dir_cluster; // Cluster number of root dir (FAT32) 
    cyg_uint32    fat_data_pos;         // Position of data area
    fatfs_type_t  fat_type;             // Type of FAT - 12, 16 or 32 
    
    cyg_io_handle_t  dev_h;           // Disk device handle
    fatfs_node_t    *root;            // Root dir node

    cyg_uint8       *bcache_mem;      // Block cache memory base
    cyg_blib_t       blib;            // Block cache and access library instance

    fatfs_node_t  node_pool_base[FATFS_NODE_POOL_SIZE]; // Node pool base   
    fatfs_node_t *node_pool[FATFS_NODE_POOL_SIZE];      // Node pool 
    cyg_uint32    node_pool_free_cnt;                   // Node pool free cnt
    
    fatfs_node_list_t  live_nlist;    // List of nodes with refcnt > 0
    fatfs_node_list_t  dead_nlist;    // List of nodes with refcnt == 0
    fatfs_hash_table_t node_hash;     // Hash of nodes in live and dead lists
} fatfs_disk_t;

    上面论述的是FAT文件系统最重要的几个内部结构体,原始的FAT结构在相关标准参考里有详细描述,这里不再赘述。
    
    ----------------
    UNIX文件系统接口
    ----------------
    
    上面提供了FAT文件系统的数据结构,下面提供操作这些数据结构的接口函数,它们都符合UNIX标准。

// 文件系统操作Filesystem operations
static int fatfs_mount  (cyg_fstab_entry *fste, cyg_mtab_entry *mte);
static int fatfs_umount (cyg_mtab_entry *mte);
static int fatfs_open   (cyg_mtab_entry *mte, cyg_dir dir, const char *name, int mode, cyg_file *fte);
static int fatfs_unlink (cyg_mtab_entry *mte, cyg_dir dir, const char *name);
static int fatfs_mkdir  (cyg_mtab_entry *mte, cyg_dir dir, const char *name);
static int fatfs_rmdir  (cyg_mtab_entry *mte, cyg_dir dir, const char *name);
static int fatfs_rename (cyg_mtab_entry *mte, cyg_dir dir1, const char *name1, cyg_dir dir2, const char *name2 );
static int fatfs_link   (cyg_mtab_entry *mte, cyg_dir dir1, const char *name1, cyg_dir dir2, const char *name2, int type);
static int fatfs_opendir(cyg_mtab_entry *mte, cyg_dir dir, const char *name, cyg_file *fte );
static int fatfs_chdir  (cyg_mtab_entry *mte, cyg_dir dir, const char *name, cyg_dir *dir_out );
static int fatfs_stat   (cyg_mtab_entry *mte, cyg_dir dir, const char *name, struct stat *buf);
static int fatfs_getinfo(cyg_mtab_entry *mte, cyg_dir dir, const char *name, int key, void *buf, int len );
static int fatfs_setinfo(cyg_mtab_entry *mte, cyg_dir dir, const char *name, int key, void *buf, int len );

// 文件操作File operations
static int fatfs_fo_read   (struct CYG_FILE_TAG *fp, struct CYG_UIO_TAG *uio);
static int fatfs_fo_write  (struct CYG_FILE_TAG *fp, struct CYG_UIO_TAG *uio);
static int fatfs_fo_lseek  (struct CYG_FILE_TAG *fp, off_t *pos, int whence );
static int fatfs_fo_ioctl  (struct CYG_FILE_TAG *fp, CYG_ADDRWORD com, CYG_ADDRWORD data);
static int fatfs_fo_fsync  (struct CYG_FILE_TAG *fp, int mode );        
static int fatfs_fo_close  (struct CYG_FILE_TAG *fp);
static int fatfs_fo_fstat  (struct CYG_FILE_TAG *fp, struct stat *buf );
static int fatfs_fo_getinfo(struct CYG_FILE_TAG *fp, int key, void *buf, int len );
static int fatfs_fo_setinfo(struct CYG_FILE_TAG *fp, int key, void *buf, int len );

// 目录操作Directory operations
static int fatfs_fo_dirread (struct CYG_FILE_TAG *fp, struct CYG_UIO_TAG *uio);
static int fatfs_fo_dirlseek(struct CYG_FILE_TAG *fp, off_t *pos, int whence);

    依次完成上面的各个函数就可以实现FAT文件系统。除了这些内容,还需要一些辅助函数,如:UNIX-DOS时间格式转换、FAT簇操作、FAT项类型判断等。
    
    -------------------------
    UNIX相对DOS文件系统的优势
    -------------------------
    
    1、标准的文件系统API接口,Linux里的大量现成源码能很容易地移植到ecos系统中;
    
    2、信息隐藏得比较好。挂装操作可以把任何介质上的任何文件系统挂装到任何目录,用户看到的只是统一的目录,不会感觉到介质差异,有利于编程操作。而DOS文件系统要指定驱动器,暴露了底层细节,程序移植性差。
    
    3、支持多任务并发。DOS是单任务的,独占系统,本质上不支持多任务。UNIX天然支持多用户多任务并发访问。
    
    --------------------
    如何使用ecos文件系统
    --------------------
    
    1、挂装
    
    文件系统使用前需要挂装操作,即把某介质上的某文件系统挂装到某目录,函数如下所示:
    mount(设备名,挂装目录,文件系统名),如:
    
    挂装RAM文件系统,设备名恒为空,挂装在根目录:
    err = mount( "", "/", "ramfs" );
    
    挂装FAT文件系统,设备名为/dev/hda/硬盘,挂装在根目录:
    err = mount( "/dev/hda/", "/", "fatfs" ); //hda后一定要加“/”,否则挂不上
    
    2、打开
    
    访问任何文件前先按某种属性打开并获得句柄。
    open(文件名,打开属性)
    
    以只读方式打开文件filename,返回句柄fd:
    fd = open(filename, O_RDONLY);
    
    3、读写
    
    (文件句柄,缓冲区,长度),返回实际读出/写入的数据长度
    
    len = read(fd, buf, len);
    len = write(fd, buf, len);
    
    4、关闭
    
    关闭文件,导致缓存数据实际写入存储设备。
    close(文件句柄)
    
    close(fd);
    
    5、卸载
    
    卸载整个文件系统,导致缓存数据实际写入存储设备。
    err = umount(挂装目录);
    
    err = umount( "/" );
    
    由上可见,《ecos增值包》文件系统的使用是相当简单容易的,www.armecos.com免费下载提供bin演示程序,有EASYARM2200或者SMARTARM2200开发板并对文件系统感兴趣的网友,可以尝试使用,该演示支持ROMFS/RAMFS/CF/SD/等介质,使用上面介绍的文件系统进行开发。除此之外,《ecos增值包》还提供其他文件系统支持,各种bootloader,多种完整TCP/IP协议栈,USB、GUI、VxWorks等内容,一揽子解决嵌入式开发遇到的各种问题。

使用特权

评论回复
9
jumpoo| | 2009-5-11 13:54 | 只看该作者

和SD卡很类似

使用特权

评论回复
10
acertm| | 2009-5-12 12:41 | 只看该作者

和SD卡一样的,查查资料

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1

主题

4

帖子

1

粉丝