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;
}
norflash的基本操作
NorFlash存储器操作指南
最新推荐文章于 2025-04-16 14:45:49 发布
本文详细介绍了在单片机环境中使用C语言进行NorFlash的基本操作,包括读取、写入和擦除等步骤,旨在帮助嵌入式硬件开发者更好地理解和应用NorFlash存储技术。
5088

被折叠的 条评论
为什么被折叠?



