SeanLib系列函数库-W25QXX
查看其它库函数说明,请点击此处跳转到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){Flash=NewW25Qxx(BITBAND_ADDR(GPIOG->ODR,11),128);if(Flash==NULL){Error_Handle(1,"Creat flash IC failed!");}Flash->ComDev=&SPI_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_WRITE==1DRESULTUSER_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_IOCTL==1DRESULTUSER_ioctl(BYTE pdrv,/* Physical drive nmuber (0..) */BYTE cmd,/* Control code */void*buff/* Buffer to send/receive control data */){/* USER CODE BEGIN IOCTL */DRESULT res=RES_OK;switch(cmd){caseCTRL_SYNC://AddLog(LogType_Debug, "SYNC");break;caseCTRL_TRIM:break;caseGET_BLOCK_SIZE://擦除块大小(扇区数)*(DWORD*)buff=8;//4096 / 512;break;caseGET_SECTOR_SIZE://扇区大小*(DWORD*)buff=512;break;caseGET_SECTOR_COUNT://总共的扇区数量*(DWORD*)buff=16384;//8 * 1024 * 1024 / 512;break;default:res=RES_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(Dev==Flash){HAL_SPI_Transmit(((W25Qxx_t*)Dev)->ComDev,Data,Len,Len);}elseif(Dev==EtherNet){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(Dev==Flash){HAL_SPI_TransmitReceive(((W25Qxx_t*)Dev)->ComDev,Send,Recv,Len,Len);}elseif(Dev==EtherNet){HAL_SPI_TransmitReceive(((W5500_t*)Dev)->ComDev,Send,Recv,Len,Len);}}#endif4. 其它说明
设备结构体中有一个指针:unsigned char * Buffer; 注释中说:为写操作开辟的内存,大小4K,当不处于写操作时,允许其它程序借用该内存
问:为什么需要这4K内存?
答:Flash的扇区大小为4K,要写入数据时,需要先将扇区中的数据全部读出来,再修改为新的数据,并将原扇区数据擦除,再写入新的数据,因此需要一个扇区的内存空间来保存原数据
问:我需要自己申请这块内存吗?
答:不需要,这块内存是在创建设备指针的时候自动分配的
