norflash的基本操作

NorFlash存储器操作指南
本文详细介绍了在单片机环境中使用C语言进行NorFlash的基本操作,包括读取、写入和擦除等步骤,旨在帮助嵌入式硬件开发者更好地理解和应用NorFlash存储技术。
norflash与mcu之间主要通过SPI接口进行通信。
norflash的初始化主要包括SPI接口的初始化、分区创建及初始化操作。

详细过程:
初始化norflash访问的SPI接口
根据名字创建分区,如果该名字分区已经存在,则不创建
如果创建分区时,空间不足,返回错误
创建新分区时,该分区与其他分区是否存在重叠
int _norflash_init(const char *name, struct norflash_dev_platform_data *pdata)//初始化
{
    log_info("norflash_init ! %x %x", pdata->spi_cs_port, pdata->spi_read_width);//打印CS引脚和位宽
    if (_norflash.spi_num == (int) - 1) {//SPI编号未初始化
        _norflash.spi_num = pdata->spi_hw_num;//编号
        _norflash.spi_cs_io = pdata->spi_cs_port;//CS引脚配置
        _norflash.spi_r_width = pdata->spi_read_width;//位宽配置
        _norflash.flash_id = 0;
        _norflash.flash_capacity = 0;//flash的存储能力
        os_mutex_create(&_norflash.mutex);//创建互斥信号量
        _norflash.max_end_addr = 0;//最大的结束地址
        _norflash.part_num = 0;//已使用的分区个数
    }
    ASSERT(_norflash.spi_num == pdata->spi_hw_num);
    ASSERT(_norflash.spi_cs_io == pdata->spi_cs_port);
    ASSERT(_norflash.spi_r_width == pdata->spi_read_width);
    struct norflash_partition *part;
    part = norflash_find_part(name);//根据名字查找part
    if (!part) {//没找到则可以根据这个name进行新建
        part = norflash_new_part(name, pdata->start_addr, pdata->size);//新建
        ASSERT(part, "not enough norflash partition memory in array\n");//空间不足
        ASSERT(norflash_verify_part(part) == 0, "norflash partition %s overlaps\n", name);//检验是否重叠
        log_info("norflash new partition %s\n", part->name);
    } else {
        ASSERT(0, "norflash partition name already exists\n");//名字已存在
    }
    return 0;
}
这里仅对指定name的分区进行了初始化操作。


norflash的打开操作首先判断是否被打开过,
没有被打开过,则需要进行SPI的初始化,
读取芯片的ID,通过芯片ID计算芯片的存储容量,
如果使能了缓存功能,则先从flash地址0处读4KB到缓存中,
将打开计数递增。
open函数主要是整个芯片的打开、初始化;init函数是分区的初始化。
int _norflash_open(void *arg)//打开
{
    int reg = 0;
    os_mutex_pend(&_norflash.mutex, 0);
    log_info("norflash open\n");
    if (!_norflash.open_cnt) {//如果open cnt为0表示接口未打开,需初始化接口等
        spi_cs_init();//片选引脚初始化
        spi_open(_norflash.spi_num);//SPI打开
        _norflash.flash_id = _norflash_read_id();//读芯片ID
        log_info("norflash_read_id: 0x%x\n", _norflash.flash_id);//打印芯片ID
        if ((_norflash.flash_id == 0) || (_norflash.flash_id == 0xffffff)) {//读回来的为0或全F,表示错误
            log_error("read norflash id error !\n");
            reg = -ENODEV;
            goto __exit;
        }
        //id号的最后一个字节表明芯片的存储空间大小
        //通过换算计算flash存储空间
        //例如WinBond FLASH W25X16最后一个ID为0x15,W25X32最后一个ID为0x16
        //W25X16空间为2MB,W25X32为4MB
        //W25X16空间大小计算:(ID & 0xFF) - 0x10 = 5
        //                   2^5 * 64KB = 2MB
        _norflash.flash_capacity = 64 * _pow(2, (_norflash.flash_id & 0xff) - 0x10) * 1024;//计算存储空间大小
        log_info("norflash_capacity: 0x%x\n", _norflash.flash_capacity);//打印
        is4byte_mode = 0;//4字节模式配置为0
        if (_norflash.flash_capacity > 16 * 1024 * 1024) {//flash大于16M,则地址需要使用4个字节表示
            norflash_enter_4byte_addr();//使能4字节模式
            is4byte_mode = 1;
        }
#if FLASH_CACHE_ENABLE//使能缓存
//使能了缓存,就先从flash中0地址开始读4KB数据到4KB大小的缓存中
//缓存地址配置为0,与读取的flash中的地址对应
        flash_cache_buf = (u8 *)malloc(4096);//4KB缓存
        ASSERT(flash_cache_buf, "flash_cache_buf is not ok\n");
        flash_cache_addr = 4096;//先给一个大于4096的数,为了第一次将flash中前4K数据读到cache中
        _norflash_read(0, flash_cache_buf, 4096, 1);//将flash中地址0开始的4K读到缓存cache中
        flash_cache_addr = 0;//cache地址为0
#endif
        log_info("norflash open success !\n");
    }
    if (_norflash.flash_id == 0 || _norflash.flash_id == 0xffffff)  {//ID号错误
        log_error("re-open norflash id error !\n");
        reg = -EFAULT;
        goto __exit;
    }
    ASSERT(_norflash.max_end_addr <= _norflash.flash_capacity, "max partition end address is greater than flash capacity\n");
    _norflash.open_cnt++;//打开计数递增

__exit:
    os_mutex_post(&_norflash.mutex);
    return reg;
}

关闭函数是将整个norflash芯片关闭,不可再访问,除非重新打开。
//关闭norflash需判断被打开引用的次数
//递减为0才是正在关闭,否则只是打开次数递减
//关闭时如果缓存中有数据,则将缓存对应的flash中的扇区擦除
//将缓存中的数据写入到flash中对应的扇区
//释放缓存申请的4KB空间
//关闭SPI接口
int _norflash_close(void)//norflash关闭
{
    os_mutex_pend(&_norflash.mutex, 0);
    log_info("norflash close\n");
    if (_norflash.open_cnt) {//计数值递减
        _norflash.open_cnt--;
    }
    if (!_norflash.open_cnt) {//递减为0
#if FLASH_CACHE_ENABLE
        if (flash_cache_is_dirty) {//缓存脏了
            flash_cache_is_dirty = 0;
            _norflash_eraser(FLASH_SECTOR_ERASER, flash_cache_addr);
            _norflash_write_pages(flash_cache_addr, flash_cache_buf, 4096);
        }
        free(flash_cache_buf);//释放缓存
        flash_cache_buf = NULL;//指针赋值NULL
#endif
        spi_close(_norflash.spi_num);//关闭SPI接口
        spi_cs_uninit();//CS引脚配置

        log_info("norflash close done\n");
    }
    os_mutex_post(&_norflash.mutex);
    return 0;
}

读函数有两种读模式:从缓存读,从芯片读。
//addr表示flash中的地址
//buf存储读到的数据
//len表示要读的数据的长度
//cache:0表示从flash中读,1表示从cache中读
//如果从flash中读,直接通过SPI接口读数据即可
//可使用1bit输出、2bit输出、4bit输出,跟配置的硬件SPI接口、flash芯片相关
//如果是从缓存cache中读,如果需要读的数据都在cache中,则直接从cache中读出
//否则从cache中将地址匹配的数据都读出,剩余的再从flash中读
int _norflash_read(u32 addr, u8 *buf, u32 len, u8 cache)
{
    int reg = 0;
    u32 align_addr;
    os_mutex_pend(&_norflash.mutex, 0);
    /* y_printf("flash read  addr = %d, len = %d\n", addr, len); */
#if FLASH_CACHE_ENABLE//使能缓存
    if (!cache) {//1表示读缓存
        goto __no_cache1;
    }
    u32 r_len = 4096 - (addr % 4096);//从偏移地址开始,剩余可读的长度,偏移地址为对4096求余
    if ((addr >= flash_cache_addr) && (addr < (flash_cache_addr + 4096))) {//地址需要在cache地方范围内
        if (len <= r_len) {//要读的数比剩余数据短,则直接读出cache中的数据即可
            memcpy(buf, flash_cache_buf + (addr - flash_cache_addr), len);
            goto __exit;
        } else {//如果要读的数据超过cache中剩余数据,则先读完剩余数据,之后再去flash中读
            memcpy(buf, flash_cache_buf + (addr - flash_cache_addr), r_len);
            addr += r_len;//地址偏移,用于后续取flash中读
            buf += r_len;//buf偏移
            len -= r_len;//计算剩余需要读的数据长度
        }
    }
__no_cache1://不是读缓存,则直接通过SPI读flash芯片内部数据
#endif
    spi_cs_l();
    if (_norflash.spi_r_width == 2) {//2bit位宽,半双工
        spi_write_byte(WINBOND_FAST_READ_DUAL_OUTPUT);//快速读指令,使用2bit输出
        _norflash_send_addr(addr);//发送地址
        spi_write_byte(0);//8bit时钟后才能接收数据
        spi_set_width(SPI_MODE_UNIDIR_2BIT);
        spi_dma_read(buf, len);
        spi_set_width(SPI_MODE_BIDIR_1BIT);
    } else if (_norflash.spi_r_width == 4) {//4bit位宽
        spi_write_byte(0x6b);//使用4bit方式来读,Fast Read Quad Output,快速读,使用4bit输出
        _norflash_send_addr(addr);
        spi_write_byte(0);//8bit时钟后才能接收数据
        spi_set_width(SPI_MODE_UNIDIR_4BIT);
        spi_dma_read(buf, len);
        spi_set_width(SPI_MODE_BIDIR_1BIT);
    } else {
        spi_write_byte(WINBOND_FAST_READ_DATA);//使用快速读,比普通读时钟可以更高
        _norflash_send_addr(addr);
        spi_write_byte(0);//8bit时钟后才能接收数据
        spi_dma_read(buf, len);
    }
    spi_cs_h();//片选拉高
//#if FLASH_CACHE_ENABLE
//    if (!cache) {
//        goto __no_cache2;
//    }
//    align_addr = (addr + len) / 4096 * 4096;
//    if ((int)len - (int)((addr + len) - align_addr) >= 4096) {
//        align_addr -= 4096;
//        if (flash_cache_addr != align_addr) {
//            flash_cache_addr = align_addr;
//            memcpy(flash_cache_buf, buf + (align_addr - addr), 4096);
//        }
//    }
//__no_cache2:
//#endif
__exit:
    os_mutex_post(&_norflash.mutex);
    return reg;
}

页写函数,往芯片中写,以页为单位写。
//addr:地址
//buf:需要写的数据
//len:需要写的数据长度
//返回1表示失败
//    0表示成功
//flash仅支持页编程,每页256字节,每个页最多写256字节
//如果一次写的数据超过256字节,将覆盖刚刚写的256字节数据
//因为时钟在继续,因此写完len个数据,必须把片选禁能,否则flash认为还在写
static int _norflash_write_pages(u32 addr, u8 *buf, u32 len)
{
    /* y_printf("flash write addr = %d, num = %d\n", addr, len); */

    int reg;
    u32 first_page_len = 256 - (addr % 256);
    first_page_len = len > first_page_len ? first_page_len : len;
    _norflash_send_write_enable();//使能写
    spi_cs_l();//片选使能
    spi_write_byte(WINBOND_PAGE_PROGRAM);//发送编程指令
    _norflash_send_addr(addr) ;//发送地址
    spi_dma_write(buf, first_page_len);//使用dma写
    spi_cs_h();//片选禁能
    reg = _norflash_wait_ok();//等待完成
    if (reg) {//flash忙
        return 1;//忙则返回1
    }
    addr += first_page_len;//计算剩余数据地址、数据头、剩余长度
    buf += first_page_len;
    len -= first_page_len;
    while (len) {//还有数据没发
        u32 cnt = len > 256 ? 256 : len;//flash每页256字节,一次仅能最多写一页
        _norflash_send_write_enable();//使能写
        spi_cs_l();//片选使能
        spi_write_byte(WINBOND_PAGE_PROGRAM);//发送编程指令
        _norflash_send_addr(addr) ;//发送地址
        spi_dma_write(buf, cnt);//使用dma写
        spi_cs_h();//片选禁能
        reg = _norflash_wait_ok();//等待完成
        if (reg) {//flash忙
            return 1;//忙则返回1
        }
        addr += cnt;//计算剩余数据地址、数据头、剩余长度
        buf += cnt;
        len -= cnt;
    }
    return 0;//编程完成,返回0
}

#if FLASH_CACHE_ENABLE//缓存的使能
static void _norflash_cache_sync_timer(void *priv)//定时将cache写入flash
{
    int reg = 0;
    os_mutex_pend(&_norflash.mutex, 0);
    if (flash_cache_is_dirty) {//cache数脏了
        flash_cache_is_dirty = 0;
        reg = _norflash_eraser(FLASH_SECTOR_ERASER, flash_cache_addr);//擦除cache地址对应的扇区
        if (reg) {
            goto __exit;
        }
        reg = _norflash_write_pages(flash_cache_addr, flash_cache_buf, 4096);//将cache数据写入flash
    }
    if (flash_cache_timer) {
        sys_timeout_del(flash_cache_timer);
        flash_cache_timer = 0;
    }
__exit:
    os_mutex_post(&_norflash.mutex);
}
#endif

写函数,有两种模式,一种是从缓存写flash芯片,一种是从buf写flash芯片。
//cache:0表示将buf中数据写入flash中
//       1表示将cache中数据写入flash中
//从buf写flash,将不进行擦除操作,直接将数据写入到flash中,按页编程,一次256字节
//从cache中写入flash
//将执行4KB扇区读、擦除、编程操作。编程按页编程,一次256字节。
//flash编程前必须进行擦除操作,因此从buf写之前必须执行擦除操作。
int _norflash_write(u32 addr, void *buf, u32 len, u8 cache)
{
    int reg = 0;
    os_mutex_pend(&_norflash.mutex, 0);

    u8 *w_buf = (u8 *)buf;
    u32 w_len = len;

    /* y_printf("flash write addr = %d, num = %d\n", addr, len); */
#if FLASH_CACHE_ENABLE//使能了cache
    if (!cache) {//buf写入flash
        reg = _norflash_write_pages(addr, w_buf, w_len);
        goto __exit;
    }
    //cache写入flash
    //如果addr是4KB地址对齐的,则align_addr = addr
    //此时align_len = 4096或w_len,如果是w_len,则说明w_len小于等于4096
    //即写入的数据量小于4KB
    //如果addr不是4KB地址对齐的,则align_addr是addr所在4KB扇区的首地址
    //即align_addr是4KB地址对齐的。align_addr肯定是小于addr的。
    //4096 - (addr - align_addr)表示从addr到addr所在4KB扇区尾地址,这段空间的字节数
    //如果w_len小于等于上述字节数,则align_len = w_len,即所写数据地址和长度都在4KB扇区内
    //否则align_len为4096 - (addr - align_addr),即从addr到尾地址的字节数
    //综上所述,align_len取值:
    //addr + w_len没有超过尾地址,align_len = w_len
    //超过尾地址:align_len = 4096 - (addr - align_addr),当addr时4KB地址对齐时,长度为4096
    //addr + align_len一定是小于等于尾地址的
    u32 align_addr = addr / 4096 * 4096;//计算首地址,即4KB地址对齐首地址
    u32 align_len = 4096 - (addr - align_addr);//计算addr到尾地址长度
    align_len = w_len > align_len ? align_len : w_len;//与要写入数据长度比对
    if (align_addr != flash_cache_addr) {//如果此时cache的地址不等于align_addr
        if (flash_cache_is_dirty) {//如果cache脏了,就将cache中数据写入对应地址flash中
            flash_cache_is_dirty = 0;
            reg = _norflash_eraser(FLASH_SECTOR_ERASER, flash_cache_addr);//擦除cache地址对应的扇区
            if (reg) {
                goto __exit;
            }
            reg = _norflash_write_pages(flash_cache_addr, flash_cache_buf, 4096);//将cache数据写入flash
            if (reg) {
                goto __exit;
            }
        }
        //cache中数据处理完了,将align_addr读4KB数到cache
        _norflash_read(align_addr, flash_cache_buf, 4096, 0);//从align_addr读4KB数到cache
        flash_cache_addr = align_addr;//赋值,cache地址和align_addr地址相同
    }
    //上述已将align_addr扇区数据与cache同步
    //根据addr地址,将cache中部分数据更新
    //更新位置偏移量:addr - align_addr,align_addr是小于等于addr的
    //数据来源:w_buf
    //更新长度:align_len
    memcpy(flash_cache_buf + (addr - align_addr), w_buf, align_len);
    //根据上述分析,addr + align_len小于尾地址或者等于尾地址
    if ((addr + align_len) % 4096) {//小于尾地址,需要处理的数据在一个扇区内
        flash_cache_is_dirty = 1;//flash脏了
        if (flash_cache_timer) {//cache定时器已经分配ID,或者说已经启动了
       //重新置超时值,即在超时同步cahce和flash中数据之前,cache中数据又更新了
            sys_timer_re_run(flash_cache_timer);
        } else {
            //sys_timeout_add:sys_timer超时增加接口
            //0表示私有参数
            //_norflash_cache_sync_timer超时后的回调函数
            //FLASH_CACHE_SYNC_T_INTERVAL:超时的值
            //返回值为定时器ID,即flash_cache_timer等于该ID
            flash_cache_timer = sys_timeout_add(0, _norflash_cache_sync_timer, FLASH_CACHE_SYNC_T_INTERVAL);
        }
    } else {//等于尾地址
    //等于尾地址
    //首先将addr所在的扇区的4KB数据从cache中更新到flash中,完成该扇区数据更新,其他剩余数据在其他扇区
    //根据之前分析可知:
    //首先将flash中addr所在的4KB扇区数据读到cache中
    //然后通过w_buf更新cache中部分数据
    //再擦除flash中4KB数据
    //在将cache中4KB数据写入flash中addr所在的4KB扇区中
    //完成读、更新、擦除、写操作,对align_addr~addr这段地址数据进行了保持
        flash_cache_is_dirty = 0;//cache不脏
        reg = _norflash_eraser(FLASH_SECTOR_ERASER, align_addr);//擦除
        if (reg) {
            goto __exit;
        }
        reg = _norflash_write_pages(align_addr, flash_cache_buf, 4096);//将cache中数据写入flash
        if (reg) {
            goto __exit;
        }
    }
    addr += align_len;//计算剩余数据地址、w_buf中位置、长度
    w_buf += align_len;
    w_len -= align_len;
    //如果addr + align_len小于等于尾地址
    //w_len = 0
    while (w_len) {
        //w_len大于4096,则当前扇区需要写的数量cnt = 4096
        //如果小于等于4096,则cnt = w_len,当前扇区能写完
        u32 cnt = w_len > 4096 ? 4096 : w_len;
        _norflash_read(addr, flash_cache_buf, 4096, 0);//从flash中读到cache,读4KB
        flash_cache_addr = addr;//cache地址更新
        memcpy(flash_cache_buf, w_buf, cnt);//cache数据更新
        if ((addr + cnt) % 4096) {//小于尾地址,需要处理的数据在一个扇区内
            flash_cache_is_dirty = 1;
            if (flash_cache_timer) {
                sys_timer_re_run(flash_cache_timer);
            } else {
                flash_cache_timer = sys_timeout_add(0, _norflash_cache_sync_timer, FLASH_CACHE_SYNC_T_INTERVAL);
            }
        } else {//等于尾地址
            flash_cache_is_dirty = 0;
            reg = _norflash_eraser(FLASH_SECTOR_ERASER, addr);//擦除
            if (reg) {
                goto __exit;
            }
            reg = _norflash_write_pages(addr, flash_cache_buf, 4096);//写入
            if (reg) {
                goto __exit;
            }
        }
        addr += cnt;//计算剩余数据地址、w_buf中位置、长度
        w_buf += cnt;
        w_len -= cnt;
    }
#else
    reg = _norflash_write_pages(addr, w_buf, w_len);
#endif
__exit:
    os_mutex_post(&_norflash.mutex);
    return reg;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值