查看其它库函数说明请点击此处跳转到SeanLib主页1. 本篇内容本篇介绍一个器件库针对W25Q系列的Flash存储芯片本库将常用操作抽象出来提供了读、写的基本操作其中写操作会自动检测是否需要擦除若需要擦除会选择最小的擦除单元。2. 使用说明本库包含W25QXX.h 和 W25QXX.lib 两个文件同时依赖SeanLib国的Sean_Send、Sean_SendRecv、Sean_GetTick三个函数。器件结构体如下typedefstruct{unsignedintTag;//提供一个变量可存储一些额外的信息如设备类型unsignedintCS_Pin;//指定该芯片使用的CS引脚的BITBAND地址unsignedintSize;//只读Flash容量单位为ByteunsignedcharManufacturer_ID;//只读制造商ID执行Get_ID后会被更新unsignedcharDevice_ID;//只读器件ID执行Get_ID后会被更新unsignedshortBackup;//未使用unsignedchar*Buffer;//为写操作开辟的内存大小4K当不处于写操作时允许其它程序借用该内存void*ComDev;//提供一个万能指针可存放与该器件通讯的SPI设备指针void(*Dispose)(void);//函数指针释放设备占用的内存/******************************************************************************* * 功 能: 读制造商ID和器件ID可用于检测芯片通讯是否正常 * 参 数: 无 * 返回值: 无 *******************************************************************************/void(*Get_ID)(void);/******************************************************************************* * 功 能: 全片擦除耗时较长 * 参 数: 无 * 返回值: 无 *******************************************************************************/void(*Erase_Chip)(void);/******************************************************************************* * 功 能: 读数据函数 * 参 数: 数据指针、起始地址、读取长度 * 返回值: 成功返回1失败返回0 *******************************************************************************/Sean_Result_t(*Read)(unsignedchar*,unsignedint,unsignedint);/******************************************************************************* * 功 能: 写数据函数写函数会自动检测是否需要擦除会选择最小的擦除方式 * 参 数: 数据指针、起始地址、写入长度 * 返回值: 成功返回1失败返回0 *******************************************************************************/Sean_Result_t(*Write)(constunsignedchar*,unsignedint,unsignedint);}W25Qxx_t;全片擦除的时间取决于容量一般不需要执行全片擦除。创建设备的函数声明如下/******************************************************************************* * 功 能: 创建ADS器件设备指针 * 参 数: * PinAddr_CS : 片选引脚如 BITBAND_ADDR(GPIOD-ODR, 5) * MBits : Flash容量单位为Mbit如W25Q128为128MBits * 返回值创建成功返回设备指针否则返回空指针NULL *******************************************************************************/W25Qxx_t*NewW25Qxx(unsignedintPinAddr_CS,unsignedintMBits);3. 示例代码3.1 创建设备指针voidW25QXX_Init(void){FlashNewW25Qxx(BITBAND_ADDR(GPIOG-ODR,11),128);if(FlashNULL){Error_Handle(1,Creat flash IC failed!);}Flash-ComDevSPI_FLASH;}MSH_INIT_EXPORT(2,W25QXX_Init,”Creat Spi flash“);3.2 读写操作下面展示了在Flash芯片上挂载文件系统时user_diskio.c中需要修改的代码其实就是FATFS通过这几个函数来读写存储器/** * brief Reads Sector(s) * param pdrv: Physical drive number (0..) * param *buff: Data buffer to store read data * param sector: Sector address (LBA) * param count: Number of sectors to read (1..128) * retval DRESULT: Operation result */DRESULTUSER_read(BYTE pdrv,/* Physical drive nmuber to identify the drive */BYTE*buff,/* Data buffer to store read data */DWORD sector,/* Sector address in LBA */UINT count/* Number of sectors to read */){/* USER CODE BEGIN READ *///AddLog(LogType_Debug, Read, Sector:%d, Count:%d, sector, count);sector*512;//将扇区编号转换为存储器地址count*512;//将扇区数量转换为数据数量if(Flash-Read(buff,sector,count)!Sean_PASS){returnRES_ERROR;}returnRES_OK;/* USER CODE END READ */}/** * brief Writes Sector(s) * param pdrv: Physical drive number (0..) * param *buff: Data to be written * param sector: Sector address (LBA) * param count: Number of sectors to write (1..128) * retval DRESULT: Operation result */#if_USE_WRITE1DRESULTUSER_write(BYTE pdrv,/* Physical drive nmuber to identify the drive */constBYTE*buff,/* Data to be written */DWORD sector,/* Sector address in LBA */UINT count/* Number of sectors to write */){/* USER CODE BEGIN WRITE *//* USER CODE HERE *///AddLog(LogType_Debug, Write, Sector:%d, Count:%d, sector, count);sector*512;//将扇区编号转换为存储器地址count*512;//将扇区数量转换为数据数量if(Flash-Write(buff,sector,count)!Sean_PASS){returnRES_ERROR;}returnRES_OK;/* USER CODE END WRITE */}#endif/* _USE_WRITE 1 *//** * brief I/O control operation * param pdrv: Physical drive number (0..) * param cmd: Control code * param *buff: Buffer to send/receive control data * retval DRESULT: Operation result */#if_USE_IOCTL1DRESULTUSER_ioctl(BYTE pdrv,/* Physical drive nmuber (0..) */BYTE cmd,/* Control code */void*buff/* Buffer to send/receive control data */){/* USER CODE BEGIN IOCTL */DRESULT resRES_OK;switch(cmd){caseCTRL_SYNC://AddLog(LogType_Debug, SYNC);break;caseCTRL_TRIM:break;caseGET_BLOCK_SIZE://擦除块大小扇区数*(DWORD*)buff8;//4096 / 512;break;caseGET_SECTOR_SIZE://扇区大小*(DWORD*)buff512;break;caseGET_SECTOR_COUNT://总共的扇区数量*(DWORD*)buff16384;//8 * 1024 * 1024 / 512;break;default:resRES_PARERR;break;}returnres;/* USER CODE END IOCTL */}#endif/* _USE_IOCTL 1 */可以看到其中的读写数据调用的下面两个方法//从地址sector非扇区地址而是存储单元的地址开始读取count个字节写入到buff中Flash-Read(buff,sector,count);//从地址sector处开始写入buff中的数据共写入count个字节Flash-Write(buff,sector,count)不使用FATFS时直接用上面这两个方法即可操作存储器。GetID的方法// 读制造商ID和器件IDvoidFlash_GetID(char*Payload){Flash-Get_ID();Printf_To_PC(OK,%s,Manufacturer ID:%02X, Device ID:%02X\r\n_,__FUNCTION__,Flash-Manufacturer_ID,Flash-Device_ID);}MSH_CMD_EXPORT(Flash_GetID,Get ID of W25Q128);3.3 外部函数打开 SeanLib.c 文件找到 #ifdef USE_SendRecv编写如下代码//当某些库需要使用外部数据发送和接收函数时需要实现下面的函数#ifdefUSE_SendRecv//数据发送函数//参数:// Dev: 调用该函数的设备函数中可根据该指针判断要将数据发送到哪个通讯端口// Data: 要发送的数据指针// Len: 要发送的数据长度//返回值:无voidSean_Send(void*Dev,unsignedchar*Data,unsignedshortLen){if(DevFlash){HAL_SPI_Transmit(((W25Qxx_t*)Dev)-ComDev,Data,Len,Len);}elseif(DevEtherNet){HAL_SPI_Transmit(((W5500_t*)Dev)-ComDev,Data,Len,Len);}}//数据接收和发送函数适用于SPI通讯的设备//参数:// Dev: 调用该函数的设备可根据该指针判断要将数据发送到哪个通讯端口// Send: 要发送的数据的存放指针// Recv: 接收到的数据的存放指针// Len: 传输的数据长度//返回值: 无voidSean_SendRecv(void*Dev,unsignedchar*Send,unsignedchar*Recv,unsignedshortLen){//示例ADS1118芯片的SPI发送和接收函数// HAL_SPI_TransmitReceive(((ADS1118_t*)Dev)-ComDev, Send, Recv, Len, Len);//示例ADS114芯片的SPI发送和接收函数// HAL_SPI_TransmitReceive(((ADS114_t*)Dev)-ComDev, Send, Recv, Len, Len);if(DevFlash){HAL_SPI_TransmitReceive(((W25Qxx_t*)Dev)-ComDev,Send,Recv,Len,Len);}elseif(DevEtherNet){HAL_SPI_TransmitReceive(((W5500_t*)Dev)-ComDev,Send,Recv,Len,Len);}}#endif4. 其它说明设备结构体中有一个指针unsigned char * Buffer; 注释中说为写操作开辟的内存大小4K当不处于写操作时允许其它程序借用该内存问为什么需要这4K内存答Flash的扇区大小为4K要写入数据时需要先将扇区中的数据全部读出来再修改为新的数据并将原扇区数据擦除再写入新的数据因此需要一个扇区的内存空间来保存原数据问我需要自己申请这块内存吗答不需要这块内存是在创建设备指针的时候自动分配的