i.MX RT1052 之按 bytes 改写外部 QSPI Flash 数据

 一、擦写外部 Nor Flash

擦写外部 Nor Flash 的功能在 SDK 的例程 flexspi_nor_polling_transfer 中有演示,该例程示范了如何擦除 chip、擦除一个 sector、编写一个 page 等。在实际应用中,可能我们遇到更多的情况可能不是擦写 page、sector,而是仅仅改写几个 bytes 的 Flash 数据。此时就需要自己再将例程按 page 操作的 API 封装成按 bytes 操作的函数,以下内容为该功能的实现提供一个参考思路。

这里我们以 RT1050 EVK 为例,使用 W25Q128JW 的 QSPI Flash,该 Flash 为 16M bytes 大小,其中,一个 block 为 64K bytes,一个 sector 为 4K bytes,一个 page 为 256 bytes。

二、思路分析

由于擦除命令最小范围是一个 sector (4K bytes),所以当我们需要改写某几个 bytes 时,需要将这几个 bytes 所在的整个 sector 擦除。又由于除了这几个 bytes 以外的数据,我们无需更改,所以在擦除整个 sector 之前,需要将 sector 原来旧有的数据先读出来保存着,接着在写入几个 bytes 的新数据时,将其他位置不用更改的旧数据一起写入。从这里可以看到,虽然从最终结果来看,是只改写了几个 bytes 的数据,但在实际操作上,是擦除了整个 sector,然后重新写入了这个 sector。

三、封装函数

参考原有的按 page 写入的函数,按照上述思路,我们来封装自己的按字节改写的函数。

//---------------------------------------------------------------------
// 按字节改写 flash 中的旧有数据
// base: FLEXSPI peripheral base address
// dstaddr: 相对 flash 开头的地址,如 0x10 0000 表示绝对地址 0x6010 0000
// src: 指向要写入的数据
// datasize: 要写入的数据字节数
//---------------------------------------------------------------------
status_t flexspi_nor_write_bytes(FLEXSPI_Type *base, uint32_t dstaddr, const uint32_t *src, uint32_t datasize)
{
status_t status = kStatus_Success;
uint32_t write_bytes_offset = 0;
uint32_t write_bytes = datasize; //要写入的字节数
uint32_t addr_write = dstaddr; //要写入的地址

uint16_t sector_start = (uint16_t)(dstaddr / SECTOR_SIZE); //写入起始 sector 的序号
uint16_t sector_end = (uint16_t)((dstaddr + datasize) / SECTOR_SIZE); //写入结束 sector 的序号
uint16_t sector_num = sector_start; //要写入的 sector 的序号

PRINTF("write_addr = 0x%8x, write_bytes = 0x%x\r\n", addr_write, write_bytes);
PRINTF("sector_start = %d, sector_end = %d, sector_num = %d\r\n", sector_start, sector_end, sector_num);

uint8_t data_sector[4*1024];

for(sector_num = sector_start; sector_num <= sector_end; sector_num++)
{
//读取将要擦除的 sector 的旧数据
memcpy(data_sector, (void *)(EXAMPLE_FLEXSPI_AMBA_BASE + sector_num * SECTOR_SIZE), sizeof(data_sector));

//擦除 sector
status = flexspi_nor_flash_erase_sector(EXAMPLE_FLEXSPI, sector_num * SECTOR_SIZE);
if (status != kStatus_Success)
{
PRINTF("Erase sector failure !\r\n");
return -1;
}
//PRINTF("Erase sector %d done. \r\n", sector_num);

DCACHE_InvalidateByRange(EXAMPLE_FLEXSPI_AMBA_BASE + sector_num * SECTOR_SIZE, FLASH_PAGE_SIZE);

//更新要写入的新值
write_bytes_offset = addr_write - sector_num * SECTOR_SIZE; //相对于该 sector 起始地址的偏移长度
//PRINTF("write_bytes_offset = 0x%x\r\n", write_bytes_offset);
if(write_bytes_offset + write_bytes < SECTOR_SIZE) //不超过此 sector
{
memcpy((void *)(&data_sector[write_bytes_offset]), (void *)(src), write_bytes);
//PRINTF("1 memcpy() addr = &data_sector[%d], size = %d\r\n", write_bytes_offset, write_bytes);
write_bytes = 0;
}
else //超过此 sector
{
memcpy((void *)(&data_sector[write_bytes_offset]), (void *)(src), (SECTOR_SIZE - write_bytes_offset));
//PRINTF("2 memcpy() addr = &data_sector[%d], size = %d\r\n", write_bytes_offset, (SECTOR_SIZE - write_bytes_offset));
write_bytes = write_bytes - (SECTOR_SIZE - write_bytes_offset);
addr_write = (sector_num + 1) * SECTOR_SIZE;
}
//PRINTF("write_bytes = %d, addr_write = 0x%x\r\n", write_bytes, addr_write);

//将新值写入该 sector
for(uint8_t i = 0; i < SECTOR_SIZE/FLASH_PAGE_SIZE; i++)
{
status = flexspi_nor_flash_page_program(EXAMPLE_FLEXSPI, sector_num * SECTOR_SIZE + i * FLASH_PAGE_SIZE, (void *)(&data_sector[i*256]));

if (status != kStatus_Success)
{
PRINTF("Page program failure !\r\n");
PRINTF("sector_num = %d, page_num = %d\r\n", sector_num, i);
return -1;
}
//PRINTF("Page program done. sector_num = %d, page_num = %d\r\n", sector_num, i);
DCACHE_InvalidateByRange(EXAMPLE_FLEXSPI_AMBA_BASE + sector_num * SECTOR_SIZE + i * FLASH_PAGE_SIZE, FLASH_PAGE_SIZE);
}
PRINTF("Write sector %d done. \r\n\r\n", sector_num);
}

return status;
}

接着我们来测试一下该函数,为了判断函数兼容性,我们选择两个 sector 的范围来测试,比如 sector 2304 ~ 2305,即对应 RT1052 的绝对地址 0x60900000 ~ 0x60901FFF。

首先先擦掉这两个 sector,防止原有数据干扰。


图 1. 擦除 sector sector 2304 ~ 2305

然后用该函数将 2304 sector 的最后 1K bytes 和 2035 sector 整个 4K bytes 写为 0xAA,即总共写了 5K bytes。


图 2. 写入 5K bytes 的 0xAA

再用该函数往 0x60900FF0 (测试地址自己选定,不必按 sector 或 page 对齐)写入 200 bytes,写入内容为:0、1、2、3、…… 、199。


图 3. 写入 200 bytes 的 0 ~ 199

上述文字描述可能不够清晰明了,具体写入的 sector 范围和详细地址如下图所示。



图 4. QSPI Flash 擦写地址和内容

从串口的打印来看,擦写都是成功的,如下图。

图 5. 串口打印

我们还可以将 Flash 的 0x60900000 ~ 0x60904000 的 4 个 sector 的数据读取出来,验证一下我们上面的擦写操作有没有成功,如下图。

……

图 6. read back 验证

这里可以看到 0xAA 的范围为 0x60900C00 ~ 0x60901FFF,与我们的写入地址 0x60900C00 和写入大小 5K bytes 一致。


图 7. read back 验证

接着看我们后面写入的 0 ~ 199,可以看到范围是 0x60900FF0 ~ 0x609010B7,与我们写入的起始地址 0x60900FF0 与写入大小 200 bytes 一致,数据内容 0x00 ~ 0Xc7 也与 0 ~ 199 一致。 

四、参考资料

(1)IMXRT1050RM.pdf

https://www.nxp.com/webapp/sps/download/preDownload.jsp?render=true

(2)W25Q128JW Datasheet,网址:

https://atta.szlcsc.com/upload/public/pdf/source/20210205/C2689660_0A4E5314E2A3675FAC38C43AF39F6008.pdf

技术文档

类型标题档案
软件flexspi_nor_polling_transfer

★博文内容均由个人提供,与平台无关,如有违法或侵权,请与网站管理员联系。

★文明上网,请理性发言。内容一周内被举报5次,发文人进小黑屋喔~

评论