nor_flash外部flash(w25q128)操作 关于外部flash的读写操作擦除操作w25q128W25Q128简介本次我使用的是w25q128芯片w25q系列的代码操作大致一样包括还有一些BY25\NM25也是一样.本文主要提供代码。需要注意的点1.需要配置SPI以及gpioflash的cs引脚2.代码里面连续写数据对于分页写入的操作已经考虑到了3.写入之前写过的页的的时候需要先擦除该页这里代码我又做了一些优化具体看stm32h7驱动spidma读写外部flash代码实现w25q128.c/** * file w25q128.c * brief 对于w25q128的读取写入操作 * */ #include w25q128.h SPI_HandleTypeDef spiFlashHandle; /** * brief 初始化flash * param hspi4 选择哪个spi */ void SPI_FLASH_Init(SPI_HandleTypeDef hspi4) { spiFlashHandle hspi4; SPI_FLASH_CS_HIGH(); } /** * brief 拉低片选使能flash */ void SPI_FLASH_CS_LOW(void) { HAL_GPIO_WritePin(SPI4_CS_GPIO_Port, SPI4_CS_Pin, GPIO_PIN_RESET); } /** * brief 拉高片选flash进入待机 */ void SPI_FLASH_CS_HIGH(void) { HAL_GPIO_WritePin(SPI4_CS_GPIO_Port, SPI4_CS_Pin, GPIO_PIN_SET); } /** * brief 执行一次flash的收发 * param txdata 发送的字节 * retval uint8_t 返回值是接收到的值 */ uint8_t SPI_FLASH_ReadWrite(uint8_t txdata) { uint8_t rxdata; HAL_SPI_TransmitReceive(spiFlashHandle, txdata, rxdata, 1, 1000); return rxdata; } /** * brief 读芯片的整体ID * note 读取制造商 IDManufacturer ID与设备 IDDevice ID组合16-bit * retval uint16_t 读到的ID */ uint16_t SPI_FLASH_ReadManufactDeviceID(void) { uint16_t deviceid; SPI_FLASH_CS_LOW(); SPI_FLASH_ReadWrite(W25X_ManufactDeviceID); /* 发送读 ID 命令 */ SPI_FLASH_ReadWrite(0); /* 写入一个字节 */ SPI_FLASH_ReadWrite(0); SPI_FLASH_ReadWrite(0); deviceid SPI_FLASH_ReadWrite(0xFF) 8; /* 读取高8位字节 */ deviceid | SPI_FLASH_ReadWrite(0xFF); /* 读取低8位字节 */ SPI_FLASH_CS_HIGH(); return deviceid; } /** * brief 读芯片的设备ID * note 读取设备 IDDevice ID * retval uint16_t 读到的ID */ uint16_t SPI_FLASH_ReadDeviceID(void) { uint32_t Temp 0; SPI_FLASH_CS_LOW(); SPI_FLASH_ReadWrite(W25X_DeviceID); SPI_FLASH_ReadWrite(0); SPI_FLASH_ReadWrite(0); SPI_FLASH_ReadWrite(0); Temp SPI_FLASH_ReadWrite(0); SPI_FLASH_CS_HIGH(); return Temp; } /** * brief 读取完整 JEDEC ID * note 3 字节Manufacturer Memory Type Capacity * retval uint32_t 读到的ID */ uint32_t SPI_FLASH_ReadJedecDeviceID(void) { uint32_t Temp 0, Temp0 0, Temp1 0, Temp2 0; SPI_FLASH_CS_LOW(); SPI_FLASH_ReadWrite(W25X_JedecDeviceID); Temp0 SPI_FLASH_ReadWrite(0); Temp1 SPI_FLASH_ReadWrite(0); Temp2 SPI_FLASH_ReadWrite(0); Temp (Temp0 16) | (Temp1 8) | Temp2; SPI_FLASH_CS_HIGH(); return Temp; } /** * brief 设置 Flash 写使能锁存器 * note 所有写/擦除操作前必须调用否则 Flash 拒绝执行 */ void SPI_FLASH_WriteEnable(void) { SPI_FLASH_CS_LOW(); SPI_FLASH_ReadWrite(W25X_WriteEnable);/* 发送写使能命令*/ SPI_FLASH_CS_HIGH(); } /** * brief 进入低功耗模式 * note 适用场景电池供电设备需延长续航时使用。 */ void SPI_Flash_PowerDown(void) { SPI_FLASH_CS_LOW(); SPI_FLASH_ReadWrite(W25X_PowerDown); /* 发送掉电命令 */ SPI_FLASH_CS_HIGH(); } /** * brief 唤醒 Flash * note 适用场景电池供电设备需延长续航时使用。 */ void SPI_Flash_WAKEUP(void) { SPI_FLASH_CS_LOW(); SPI_FLASH_ReadWrite(W25X_ReleasePowerDown);/* 发送上电命令 */ SPI_FLASH_CS_HIGH(); } /** * brief 擦除指定扇区4KB地址需为扇区对齐 * param SectorAddr 扇区地址 */ void SPI_FLASH_SectorErase(uint32_t SectorAddr) { /* 发送FLASH写使能命令 */ SPI_FLASH_WriteEnable(); SPI_FLASH_WaitForWriteEnd(); SPI_FLASH_CS_LOW(); /*发送扇区擦除指令*/ SPI_FLASH_ReadWrite(W25X_SectorErase); /*发送擦除扇区地址的高位*/ SPI_FLASH_ReadWrite((SectorAddr 0xFF0000) 16); /* 发送擦除扇区地址的中位 */ SPI_FLASH_ReadWrite((SectorAddr 0xFF00) 8); /* 发送擦除扇区地址的低位 */ SPI_FLASH_ReadWrite(SectorAddr 0xFF); SPI_FLASH_CS_HIGH(); /* 等待擦除完毕*/ SPI_FLASH_WaitForWriteEnd(); } /** * brief 整片擦除 * note 需要等待至多30秒 */ void SPI_FLASH_Erase_all(void) { SPI_FLASH_WriteEnable(); SPI_FLASH_CS_LOW(); SPI_FLASH_ReadWrite(W25X_ChipErase); SPI_FLASH_CS_HIGH(); /* 整片擦除要 5~20 秒不能用通用 WaitForWriteEnd */ uint8_t FLASH_Status 0; uint32_t start_tick HAL_GetTick(); SPI_FLASH_CS_LOW(); SPI_FLASH_ReadWrite(W25X_ReadStatusReg); do { FLASH_Status SPI_FLASH_ReadWrite(Dummy_Byte); if ((HAL_GetTick() - start_tick) 30000) break; /* 30 秒 */ } while ((FLASH_Status WIP_Flag) SET); SPI_FLASH_CS_HIGH(); } /** * brief 等待写入完毕 */ void SPI_FLASH_WaitForWriteEnd(void) { uint8_t FLASH_Status 0; uint32_t start_tick HAL_GetTick(); SPI_FLASH_CS_LOW(); SPI_FLASH_ReadWrite(W25X_ReadStatusReg); do { FLASH_Status SPI_FLASH_ReadWrite(Dummy_Byte); /* 1 秒超时 */ if ((HAL_GetTick() - start_tick) 1000) { break; /*不 return保证下面 CS_HIGH 执行 */ } } while ((FLASH_Status WIP_Flag) SET); SPI_FLASH_CS_HIGH(); } /** * brief 按页写入 * param pBuffer 要写的数据 * param WriteAddr 写地址输入的地址需自行对其 * param NumByteToWrite 写的字节数 */ void SPI_FLASH_PageWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite) { /* 发送FLASH写使能命令 */ SPI_FLASH_WriteEnable(); /* 选择FLASH: CS低电平 */ SPI_FLASH_CS_LOW(); /* 写页写指令*/ SPI_FLASH_ReadWrite(W25X_PageProgram); /*发送写地址的高位*/ SPI_FLASH_ReadWrite((WriteAddr 0xFF0000) 16); /*发送写地址的中位*/ SPI_FLASH_ReadWrite((WriteAddr 0xFF00) 8); /*发送写地址的低位*/ SPI_FLASH_ReadWrite(WriteAddr 0xFF); if(NumByteToWrite SPI_FLASH_PerWritePageSize) /*进行限位只能是单页写入*/ { NumByteToWrite SPI_FLASH_PerWritePageSize; //MLOG_W(spi,SPI_FLASH_PageWrite too large!); } /* 写入数据*/ while (NumByteToWrite--) { /* 发送当前要写入的字节数据 */ SPI_FLASH_ReadWrite(*pBuffer); /* 指向下一字节数据 */ pBuffer; } /* 停止信号 FLASH: CS 高电平 */ SPI_FLASH_CS_HIGH(); /* 等待写入完毕*/ SPI_FLASH_WaitForWriteEnd(); } /** * brief 连续写函数已有自动分割页写入功能 * param pBuffer 要写的数据 * param WriteAddr 写地址 * param NumByteToWrite 写的字节数 * note 写地址不用可以对齐整页内部自动分割并写入 */ void SPI_FLASH_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite) { uint8_t NumOfPage 0, NumOfSingle 0, Addr 0, count 0, temp 0; Addr WriteAddr % SPI_FLASH_PageSize; count SPI_FLASH_PageSize - Addr; NumOfPage NumByteToWrite / SPI_FLASH_PageSize; NumOfSingle NumByteToWrite % SPI_FLASH_PageSize; /* Addr0,则WriteAddr 刚好按页对齐 aligned */ if (Addr 0) { /* NumByteToWrite SPI_FLASH_PageSize */ if (NumOfPage 0) { SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite); } else /* NumByteToWrite SPI_FLASH_PageSize */ { /*先把整数页都写了*/ while (NumOfPage--) { SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize); WriteAddr SPI_FLASH_PageSize; pBuffer SPI_FLASH_PageSize; } /*若有多余的不满一页的数据把它写完*/ SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle); } } /* 若地址与 SPI_FLASH_PageSize 不对齐 */ else { /* NumByteToWrite SPI_FLASH_PageSize */ if (NumOfPage 0) { /*当前页剩余的count个位置比NumOfSingle小写不完*/ if (NumOfSingle count) { temp NumOfSingle - count; /*先写满当前页*/ SPI_FLASH_PageWrite(pBuffer, WriteAddr, count); WriteAddr count; pBuffer count; /*再写剩余的数据*/ SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp); } else /*当前页剩余的count个位置能写完NumOfSingle个数据*/ { SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite); } } else /* NumByteToWrite SPI_FLASH_PageSize */ { /*地址不对齐多出的count分开处理不加入这个运算*/ NumByteToWrite - count; NumOfPage NumByteToWrite / SPI_FLASH_PageSize; NumOfSingle NumByteToWrite % SPI_FLASH_PageSize; SPI_FLASH_PageWrite(pBuffer, WriteAddr, count); WriteAddr count; pBuffer count; /*把整数页都写了*/ while (NumOfPage--) { SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize); WriteAddr SPI_FLASH_PageSize; pBuffer SPI_FLASH_PageSize; } /*若有多余的不满一页的数据把它写完*/ if (NumOfSingle ! 0) { SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle); } } } } /** * brief 连续读函数 * param pBuffer 存储数据的地址 * param WriteAddr 开始读地址 * param NumByteToWrite 读字节数 */ void SPI_FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead) { SPI_FLASH_CS_LOW(); /* 发送 读 指令 */ SPI_FLASH_ReadWrite(W25X_ReadData); /* 发送 读 地址高位 */ SPI_FLASH_ReadWrite((ReadAddr 0xFF0000) 16); /* 发送 读 地址中位 */ SPI_FLASH_ReadWrite((ReadAddr 0xFF00) 8); /* 发送 读 地址低位 */ SPI_FLASH_ReadWrite(ReadAddr 0xFF); /* 读取数据 */ while (NumByteToRead--) { /* 读取一个字节*/ *pBuffer SPI_FLASH_ReadWrite(Dummy_Byte); /* 指向下一个字节缓冲区 */ pBuffer; } SPI_FLASH_CS_HIGH(); }w25q128.h#ifndef __BSP_SPI_FLASH_H #define __BSP_SPI_FLASH_H #include stm32h7xx_hal.h #include main.h #include mlog.h #include stdio.h /* FLASH芯片列表 */ #define W25Q80 0XEF13 /* W25Q80 芯片ID */ #define W25Q16 0XEF14 /* W25Q16 芯片ID */ #define W25Q32 0XEF15 /* W25Q32 芯片ID */ #define W25Q64 0XEF16 /* W25Q64 芯片ID */ #define W25Q128 0XEF17 /* W25Q128 芯片ID */ #define W25Q256 0XEF18 /* W25Q256 芯片ID */ #define BY25Q64 0X6816 /* BY25Q64 芯片ID */ #define BY25Q128 0X6817 /* BY25Q128 芯片ID */ #define NM25Q64 0X5216 /* NM25Q64 芯片ID */ #define NM25Q128 0X5217 /* NM25Q128 芯片ID */ extern uint16_t norflash_TYPE; /* 定义FLASH芯片型号 */ /**************命令定义-开头*************/ #define W25X_WriteEnable 0x06 /*写使能指令*/ #define W25X_WriteDisable 0x04 /*写禁止指令*/ #define W25X_ReadStatusReg 0x05 /*读状态寄存器1*/ #define W25X_WriteStatusReg 0x01 /*写状态寄存器1*/ #define W25X_ReadData 0x03 /*普通读数据*/ #define W25X_FastReadData 0x0B /*快速读数据*/ #define W25X_FastReadDual 0x3B /*双SPI快速读*/ #define W25X_PageProgram 0x02 /*页编程指令*/ #define W25X_BlockErase 0xD8 /*64KB块擦除*/ #define W25X_SectorErase 0x20 /*4KB扇区擦除*/ #define W25X_ChipErase 0xC7 /*全片擦除*/ #define W25X_PowerDown 0xB9 /*掉电模式*/ #define W25X_ReleasePowerDown 0xAB /*释放掉电模式*/ #define W25X_DeviceID 0xAB /*读设备ID*/ #define W25X_ManufactDeviceID 0x90 /*读制造商设备ID*/ #define W25X_JedecDeviceID 0x9F /*读JEDEC ID*/ #define WIP_Flag 0x01 /* Write In Progress (WIP) flag */ #define Dummy_Byte 0xFF /**************命令定义-结尾*************/ #define SPI_FLASH_PageSize 256 #define SPI_FLASH_PerWritePageSize 256 // FLASH初始化 void SPI_FLASH_Init(SPI_HandleTypeDef hspi1); // 读写数据 uint8_t SPI_FLASH_ReadWrite(uint8_t txdata); // 读取ID uint16_t SPI_FLASH_ReadManufactDeviceID(void); uint16_t SPI_FLASH_ReadDeviceID(void); uint32_t SPI_FLASH_ReadJedecDeviceID(void); // 写使能 void SPI_FLASH_WriteEnable(void); // 进入掉电模式 void SPI_Flash_PowerDown(void); // 唤醒 void SPI_Flash_WAKEUP(void); // 等待WIP(BUSY)标志被置0即等待到FLASH内部数据写入完毕 void SPI_FLASH_WaitForWriteEnd(void); // 对FLASH写入数据调用本函数写入数据前需要先擦除扇区 void SPI_FLASH_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite); // 对FLASH按页写入数据调用本函数写入数据前需要先擦除扇区 void SPI_FLASH_PageWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite); // 读取FLASH数据 void SPI_FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead); // 擦除FLASH扇区 void SPI_FLASH_SectorErase(uint32_t SectorAddr); // 擦除FLASH扇区整片擦除 void SPI_FLASH_Erase_all(void); // 片选拉低 void SPI_FLASH_CS_LOW(void); // 片选拉高 void SPI_FLASH_CS_HIGH(void); #endif