今天开启一个新任务,在STM32F746 Discovery 板上,移植FileX文件系统,使用QSPI Flash作为存储媒介。板上QSPI Flash型号为 W25Q128A, 在STCube的固件库中,已有该器件的应用例程,编译运行例程来验证该Flash芯片读写正常后,按照FileX的驱动框架,实现驱动设计。
设计实现过程中,会记录相关的设计流程与技术细节,并持续更新,直到完成。
软件开发基于IAR 9.1,已正常运行了 ThreadX,并编译通过了 FileX核心文件,现在开始QSPI 驱动设计。
Flash需要读写操作,因此不能工作在内存映射模式,只能使用中断或DMA,通过接口来实现读写操作。
1. 创建驱动文件
- 参考SD卡驱动文件,创建QSPI驱动文件 “fx_stm32_qspi_driver.c”,并实现空的驱动入口函数:
VOID _fx_stm32_qspi_driver(FX_MEDIA *media_ptr)
{
}
- 驱动中,主要实现 ⑴初始化、⑵读取、⑶写入、⑷Boot读取、⑸Boot写入、⑹Flush等几个重要访问操作
- 驱动中打算基于DMA来做读写传输,因此需要在传输过程中,阻塞下一次操作,并挂起访问线程,因此使用信号量来对QSPI访问加锁
- 开发板资源中,已在驱动文件 “stm32746g_discovery_qspi.c” 中提供了底层的QSPI访问函数,因此第一步,仅在驱动中调用这几个访问函数:
VOID _fx_stm32_qspi_driver(FX_MEDIA *media_ptr)
{
UINT status;
uint8_t QSPI_Status;
ULONG partition_start;
ULONG partition_size;
switch(media_ptr -> fx_media_driver_request)
{
case FX_DRIVER_READ:
{
QSPI_Status = BSP_QSPI_Read(media_ptr -> fx_media_driver_buffer, (media_ptr -> fx_media_driver_logical_sector + media_ptr -> fx_media_hidden_sectors)*256, media_ptr -> fx_media_driver_sectors * media_ptr -> fx_media_bytes_per_sector);
if(QSPI_Status==QSPI_OK)
{
media_ptr -> fx_media_driver_status = FX_SUCCESS;
}
else
{
media_ptr -> fx_media_driver_status = FX_IO_ERROR;
}
break;
}
case FX_DRIVER_WRITE:
{
QSPI_Status = BSP_QSPI_Write(media_ptr -> fx_media_driver_buffer, (media_ptr -> fx_media_driver_logical_sector + media_ptr -> fx_media_hidden_sectors)*256, media_ptr -> fx_media_driver_sectors * media_ptr -> fx_media_bytes_per_sector);
if(QSPI_Status==QSPI_OK)
{
media_ptr -> fx_media_driver_status = FX_SUCCESS;
}
else
{
media_ptr -> fx_media_driver_status = FX_IO_ERROR;
}
break;
}
case FX_DRIVER_FLUSH:
{
media_ptr -> fx_media_driver_status = FX_SUCCESS;
break;
}
case FX_DRIVER_ABORT:
{
media_ptr -> fx_media_driver_status = FX_SUCCESS;
break;
}
case FX_DRIVER_INIT:
{
if(tx_semaphore_create(&semaphore_transfer, "QSPI transfer semaphore", 0) != TX_SUCCESS)
{
media_ptr -> fx_media_driver_status = FX_IO_ERROR;
break;
}
if(BSP_QSPI_Init()==QSPI_OK)
media_ptr -> fx_media_driver_status = FX_SUCCESS;
else
media_ptr -> fx_media_driver_status = FX_IO_ERROR;
break;
}
case FX_DRIVER_UNINIT:
{
tx_semaphore_delete(&semaphore_transfer);
media_ptr -> fx_media_driver_status = FX_SUCCESS;
break;
}
case FX_DRIVER_BOOT_READ:
{
QSPI_Status = BSP_QSPI_Read(media_ptr -> fx_media_driver_buffer,0,media_ptr -> fx_media_driver_sectors * 256);
if(QSPI_Status!=QSPI_OK)
{
media_ptr -> fx_media_driver_status = FX_IO_ERROR;
return;
}
partition_start = 0;
status = _fx_partition_offset_calculate(media_ptr -> fx_media_driver_buffer, 0,
&partition_start, &partition_size);
if (status)
{
media_ptr -> fx_media_driver_status = FX_IO_ERROR;
return;
}
if (partition_start)
{
QSPI_Status = BSP_QSPI_Read(media_ptr -> fx_media_driver_buffer, partition_start, media_ptr -> fx_media_driver_sectors * 256);
if(QSPI_Status!=QSPI_OK)
{
media_ptr -> fx_media_driver_status = FX_IO_ERROR;
return;
}
}
media_ptr -> fx_media_driver_status = FX_SUCCESS;
break;
}
case FX_DRIVER_BOOT_WRITE:
{
QSPI_Status = BSP_QSPI_Write(media_ptr -> fx_media_driver_buffer, (media_ptr -> fx_media_hidden_sectors)*256, media_ptr -> fx_media_driver_sectors * 256);
if(QSPI_Status==QSPI_OK)
{
media_ptr -> fx_media_driver_status = FX_SUCCESS;
}
else
{
media_ptr -> fx_media_driver_status = FX_IO_ERROR;
}
break ;
}
default:
{
media_ptr -> fx_media_driver_status = FX_IO_ERROR;
break;
}
}
}
2. 调试驱动流程
-
当应用层调用 fx_media_open 函数,打开文件系统时,对于驱动的调用流程为:
⑴ FX_DRIVER_INIT,初始化
⑵ FX_DRIVER_BOOT_READ,读取Boot信息
⑶ FX_DRIVER_UNINIT,如果读取Boot失败 ,释放系统占用资源 -
打开失败,是由于没有读取到有效的Boot内容,说明介质还没有格式化。增加格式化调用:
#define QSPI_TOTAL_BLOCKS 1
#define QSPI_PHYSICAL_SECTORS_PER_BLOCK 256 /* Min value of 2, max value of 120 for 1 sector of overhead. */
#define QSPI_WORDS_PER_PHYSICAL_SECTOR 256
fx_media_format(&qspi_disk,
_fx_stm32_qspi_driver, // Driver entry
FX_NULL, // Unused
qspi_media_memory, // Media buffer pointer
sizeof(qspi_media_memory), // Media buffer size
"QSPI_Flash_Disk", // Volume Name
1, // Number of FATs
16, // Directory Entries
0, // Hidden sectors
QSPI_PHYSICAL_SECTORS_PER_BLOCK, // Total sectors
QSPI_WORDS_PER_PHYSICAL_SECTOR, // Sector size
1, // Sectors per cluster
1, // Heads
1); // Sectors per track
根据板上的芯片 W25Q128 手册,每个扇区为256字节,使用256个扇区共64K字节空间作为文件系统,基地址为从0开始,格式化文件系统。
-
文件系统格式化时,对于驱动的调用流程:
⑴ FX_DRIVER_INIT,初始化
⑵ FX_DRIVER_BOOT_WRITE,写入Boot扇区
⑶ FX_DRIVER_WRITE,写入分区表
⑷ FX_DRIVER_UNINIT,释放系统资源 -
完成文件系统格式化后,再次重新打开文件系统,调用流程为:
⑴ FX_DRIVER_INIT,初始化
⑵ FX_DRIVER_BOOT_READ,读取Boot信息
⑶ FX_DRIVER_READ,读取文件分区表 -
完成正确的读取访问后,文件系统打开正常。
3. 测试文件系统操作
- 创建目录
fx_directory_create(diskMediaType, path);
- 创建文件
status = fx_file_create(diskMediaType, local_buffer);
- 写入文件
status = fx_file_open(diskMediaType, &file, local_buffer, FX_OPEN_FOR_WRITE);
status = fx_file_seek(&file, 0);
status = fx_file_write(&file, local_buffer, len);
status = fx_file_close(&file);
- 列出目录内容
- 读取文件
status = fx_file_open(diskMediaType, &file, pPath, FX_OPEN_FOR_WRITE);
status = fx_file_seek(&file, 0);
status = fx_file_read(&file, local_buffer, 64, &actual);
status = fx_file_close(&file);
- 删除文件
status = fx_file_delete(diskMediaType, pPath);
- 删除目录
遗留问题:
- 创建目录、文件正常,但重启后丢失,貌似没有写入到Flash芯片中
★ 跟踪调试后发现,每次写入文件后,仅调用了 FX_DRIVER_WRITE 方法写入文件内容,但并没有更新分区表的写入调用,怀疑分区表一直是缓存在内存中,没有写入到Flash中。
★ 在驱动写入调用时设置断点,调用:
UINT status = fx_media_flush(diskMediaType);
发现写入调用大量被调用,反而是在写入文件时,并不是每次都会调用写入驱动,因此可以判断,大部分写入被缓存在文件系统内部,只有在调用 Flush 时,才会实际写入Flash。
但即便调用了 Flush,在重启系统后,文件系统中还是空的,之前写入的文件及目录没有被实际读取回来,怀疑是文件分区表没有有效又加载进来。分析其原因,可能是因为写入前没有检查Flash是否为空,没有进行必要的擦除调用,导致数据可能没有真正地写入扇区。
考虑到最终是要集成 LevelX 进来,所以不再继续在这个点进行擦除调试,先集成 LevelX,再在此基础上做进一步跟踪调试。
**其他问题: **
- 格式化时,根目录文件数量设置为16,但实际创建8个文件后,就没法再创建新文件,报FX_NO_MORE_SPACE 错误
- 创建子目录,进入,创建多个文件,返回上级目录,发现子目录不见了
待实现功能:
- 移植LevelX
- 基于DMA读写数据
- 增加擦除信息输出,跟踪系统运行
4. 集成 LevelX
- 在文件系统初始化时,调用:
lx_nor_flash_initialize();
-
所谓集成,实际是在底层的Flash读写驱动中,不直接访问Flash,而是通过调用 LevelX 的访问接口,实现Flash访问操作。在 LevelX 的接口实现中,进行负载平衡处理,避免反复擦写同一位置,将数据尽量分散写入整个文件系统空间。
-
集成 LevelX 后,驱动分为两级:
⑴ 原来的 FX 驱动框架中,调用 LevelX 接口方法;
⑵ 增加 LX 的底层接口实现,实现到QSPI Flash的实际访问。 -
★ 修改后的FX驱动框架:
VOID _fx_stm32_qspi_driver(FX_MEDIA *media_ptr)
{
UCHAR *source_buffer;
UCHAR *destination_buffer;
ULONG logical_sector;
ULONG i;
UINT status;
switch(media_ptr -> fx_media_driver_request)
{
case FX_DRIVER_READ:
{
logical_sector = media_ptr -> fx_media_driver_logical_sector;
destination_buffer = (UCHAR *) media_ptr -> fx_media_driver_buffer;
for (i = 0; i < media_ptr -> fx_media_driver_sectors; i++)
{
status = lx_nor_flash_sector_read(&qspi_flash, logical_sector, destination_buffer);
if (status != LX_SUCCESS)
{
media_ptr -> fx_media_driver_status = FX_IO_ERROR;
return;
}
logical_sector++;
destination_buffer = destination_buffer + BYTES_PER_PHYSICAL_SECTOR;
}
media_ptr -> fx_media_driver_status = FX_SUCCESS;
break;
}
case FX_DRIVER_WRITE:
{
logical_sector = media_ptr -> fx_media_driver_logical_sector;
source_buffer = (UCHAR *) media_ptr -> fx_media_driver_buffer;
for (i = 0; i < media_ptr -> fx_media_driver_sectors; i++)
{
status = lx_nor_flash_sector_write(&qspi_flash, logical_sector, source_buffer);
if (status != LX_SUCCESS)
{
media_ptr -> fx_media_driver_status = FX_IO_ERROR;
return;
}
logical_sector++;
source_buffer = source_buffer + BYTES_PER_PHYSICAL_SECTOR;
}
media_ptr -> fx_media_driver_status = FX_SUCCESS;
break;
}
case FX_DRIVER_RELEASE_SECTORS:
{
logical_sector = media_ptr -> fx_media_driver_logical_sector;
for (i = 0; i < media_ptr -> fx_media_driver_sectors; i++)
{
status = lx_nor_flash_sector_release(&BYTES_PER_PHYSICAL_SECTOR, logical_sector);
if (status != LX_SUCCESS)
{
media_ptr -> fx_media_driver_status = FX_IO_ERROR;
return;
}
logical_sector++;
}
media_ptr -> fx_media_driver_status = FX_SUCCESS;
break;
}
case FX_DRIVER_FLUSH:
{
media_ptr -> fx_media_driver_status = FX_SUCCESS;
break;
}
case FX_DRIVER_ABORT:
{
media_ptr -> fx_media_driver_status = FX_SUCCESS;
break;
}
case FX_DRIVER_INIT:
{
media_ptr -> fx_media_driver_free_sector_update = FX_TRUE;
status = lx_nor_flash_open(&qspi_flash, "qspi flash", _lx_qspi_flash_initialize);
if (status != LX_SUCCESS)
{
media_ptr -> fx_media_driver_status = FX_IO_ERROR;
return;
}
media_ptr -> fx_media_driver_status = FX_SUCCESS;
break;
}
case FX_DRIVER_UNINIT:
{
status = lx_nor_flash_close(&qspi_flash);
if (status != LX_SUCCESS)
{
media_ptr -> fx_media_driver_status = FX_IO_ERROR;
return;
}
media_ptr -> fx_media_driver_status = FX_SUCCESS;
break;
}
case FX_DRIVER_BOOT_READ:
{
destination_buffer = (UCHAR *) media_ptr -> fx_media_driver_buffer;
status = lx_nor_flash_sector_read(&qspi_flash, 0, destination_buffer);
if ((destination_buffer[0] != (UCHAR) 0xEB) ||
(destination_buffer[1] != (UCHAR) 0x34) ||
(destination_buffer[2] != (UCHAR) 0x90))
{
media_ptr -> fx_media_driver_status = FX_MEDIA_INVALID;
return;
}
if (status != LX_SUCCESS)
{
media_ptr -> fx_media_driver_status = FX_IO_ERROR;
return;
}
media_ptr -> fx_media_driver_status = FX_SUCCESS;
break;
}
case FX_DRIVER_BOOT_WRITE:
{
source_buffer = (UCHAR *) media_ptr -> fx_media_driver_buffer;
status = lx_nor_flash_sector_write(&qspi_flash, 0, source_buffer);
if (status != LX_SUCCESS)
{
media_ptr -> fx_media_driver_status = FX_IO_ERROR;
return;
}
media_ptr -> fx_media_driver_status = FX_SUCCESS;
break ;
}
default:
{
media_ptr -> fx_media_driver_status = FX_IO_ERROR;
break;
}
}
}
驱动中,对于Flash的读写访问,全部修改为通过LevelX接口来实现。
- ★ 新增的LX驱动框架:
ULONG qspi_sector_memory[WORDS_PER_PHYSICAL_SECTOR];
UINT _lx_qspi_flash_read(ULONG *flash_address, ULONG *destination, ULONG words)
{
uint8_t *pBuf = (uint8_t*)destination;
uint32_t addr = (uint32_t)flash_address;
int size = (words<<2);
uint8_t QSPI_Status = BSP_QSPI_Read((uint8_t*)destination, (uint32_t)flash_address, (words<<2));
if(QSPI_Status==QSPI_OK)
{
return(LX_SUCCESS);
}
else
{
return(LX_ERROR);
}
}
UINT _lx_qspi_flash_write(ULONG *flash_address, ULONG *source, ULONG words)
{
uint8_t QSPI_Status = BSP_QSPI_Write((uint8_t*)source, (uint32_t)flash_address, (words<<2));
if(QSPI_Status==QSPI_OK)
{
return(LX_SUCCESS);
}
else
{
return(LX_ERROR);
}
}
UINT _lx_qspi_flash_block_erase(ULONG block, ULONG erase_count)
{
int addr, i;
addr = FLASH_USER_START_ADDR + block * FLASH_USER_BLOCK_SIZE;
for(i=0;i<erase_count;i++)
{
uint8_t QSPI_Status = BSP_QSPI_Erase_Block(addr);
if(QSPI_Status!=QSPI_OK)
{
return(LX_ERROR);
}
addr += FLASH_USER_BLOCK_SIZE;
}
return(LX_SUCCESS);
}
UINT _lx_qspi_flash_erase_all(VOID)
{
return _lx_qspi_flash_block_erase(0, FLASH_USER_BLOCK);
}
UINT _lx_qspi_flash_block_erased_verify(ULONG block)
{
unsigned char buf[WORDS_PER_PHYSICAL_SECTOR];
uint32_t Address = FLASH_USER_START_ADDR + (block*FLASH_USER_BLOCK_SIZE);
uint32_t EndAddress = Address + FLASH_USER_BLOCK_SIZE;
int i;
while(Address<EndAddress)
{
BSP_QSPI_Read(buf, Address, WORDS_PER_PHYSICAL_SECTOR);
for(i=0;i<WORDS_PER_PHYSICAL_SECTOR;i++)
{
if (buf[i]!= 0xFF)
return(LX_ERROR);
}
Address += WORDS_PER_PHYSICAL_SECTOR;
}
return(LX_SUCCESS);
}
UINT _lx_qspi_flash_system_error(UINT error_code, ULONG block, ULONG sector)
{
LX_PARAMETER_NOT_USED(error_code);
LX_PARAMETER_NOT_USED(block);
LX_PARAMETER_NOT_USED(sector);
return(LX_ERROR);
}
UINT _lx_qspi_flash_initialize(LX_NOR_FLASH *nor_flash)
{
nor_flash->lx_nor_flash_base_address = (ULONG*)FLASH_USER_START_ADDR;
nor_flash ->lx_nor_flash_total_blocks = FLASH_USER_BLOCK;
nor_flash ->lx_nor_flash_words_per_block = FLASH_USER_BLOCK_SIZE/sizeof(ULONG);
nor_flash ->lx_nor_flash_driver_read = _lx_qspi_flash_read;
nor_flash ->lx_nor_flash_driver_write = _lx_qspi_flash_write;
nor_flash ->lx_nor_flash_driver_block_erase = _lx_qspi_flash_block_erase;
nor_flash ->lx_nor_flash_driver_block_erased_verify = _lx_qspi_flash_block_erased_verify;
nor_flash ->lx_nor_flash_sector_buffer = &qspi_sector_memory[0];
BSP_QSPI_Init();
return(LX_SUCCESS);
}
-
驱动中,通过调用QSPI访问接口,实现Flash的底层访问。
-
基于以上代码实现,编译运行后,测试后可以创建目录、创建文件,重启后文件不丢失,应该是完成了正常的Flash写入行为
-
问题:
测试时发现,创建几个文件后,目录中出现许多名为 “.” 的项目,属性为 0x000000ff,而且数量多后导致写入发生错误 8 (FX_FILE_CORRUPT)和 10(FX_NO_MORE_SPACE)。 -
解决:
通过对源码的跟踪分析,单步调试,最终解决上述问题。
⑴ 关于根目录数量问题
虽然在格式化文件系统时,指定了根目录数量为16,但实际创建8个文件加子目录就写满问题,是由于FileX默认支持长文件名,且无法编译时取消。长文件名一条文件信息,至少占用两个目录槽位,一个用于存储短文件名,另一个存储长文件名。如果长文件名本身长度很长的话,可能会占用两个以上的多个槽位,使文件数量更加减少。所以根目录文件数量,最多为指定数值的一半,这是系统决定的,不是Bug。
⑵ 关于文件系统写入时发生的各种问题,如多出名为 “.” 的项目、文件或目录莫名消失等,都源于文件系统的设置,即体现在格式化时的参数设置上,错误的参数设置,导致了上述访问问题。 -
关于参数:
关于文件系统参数设置,有以下几处需要注意:
⑴ Sector size
开始认为,SectorSize是由用户设定的。由于W25Q128芯片本身就是以扇区为单位来读写的,每个扇区为256字节,所以就将该参数设置为256,结果导致上面各种访问问题。后来跟踪源码,发现在源码内部,有地方直接使用512作为扇区大小,导致计算块数量时错误。因此将该参数设置为512。
⑵ Total sectors
按照示例代码中的注释,应该保留一些扇区数量,因此将该参数设置为实际扇区数量 - 8。
经过以上修改,重新编译运行,格式化文件系统,之后测试创建目录、写入文件,反复重启,测试结果都正常。
-
格式化时,报错,错误号为24,单步跟踪调试后发现,是对打开的文件系统格式化时,系统报错。在调用格式化方法前,先调用 fx_media_close 关闭当前文件系统,再进行格式化后,正常。
-
当可用扇区数量为 0 时,虽然剩余空间还有,但访问出错,读取也会出错。具体问题分析处理,参考另一篇文章:FileX + LevelX中异常错误的真实原因处理
-
碎片整理问题
⑴ 在调用 lx_nor_flash_defragment 进行碎片整理时,整理完成后文件系统目录发生混乱,重启后文件系统不可访问
⑵ 在驱动程序中,将块擦除函数 _lx_qspi_flash_block_erase 中的擦除操作,强制限制擦除数量为 1,问题解决:UINT _lx_qspi_flash_block_erase(ULONG block, ULONG erase_count) { return qspiflash_erase_block(block, 1); }
具体原因没详查,只是跟踪调用栈时,看到擦除调用:
/* Erase the entire block. */ status = _lx_nor_flash_driver_block_erase(nor_flash, erase_block, erase_count+1);
调用的擦除数量有加1操作,单步调试发现调用时,擦除数量都是2,所以强制将其设置为擦除为1个块。
至此,文件系统FileX加LevelX在 QSPI Flash 上运行正常。
之后会做一定的负载与可靠性测试。
待续 … …