1、硬件电路
需要系统性的看一下W25Q256芯片手册
2、设置RCC,选择高速外部时钟HSE,时钟设置为180MHz
3、配置SPI
4、生成工程配置
5、读写流程图
5、相关代码
/**
* W25Q256JV FLASH芯片有256M bit总共32M byte存储空间
* W25Q256JV的存储阵列被组织成131072个可编程页面,每个页面256字节。每次最多可以编程256字节
* 扇区大小为:16 * page 即 16 *256 = 4KB字节。
* 块大小为:128 * page 即 128 *256 = 32KB字节。或者是 256 * page 即 256 * 256 = 64KB 字节。
* 擦除:W25Q256JV芯片可以以4KB扇区为单位进行擦除,也可以以32KB块为单位进行擦除/64KB块为单位进行擦除,或者整个芯片为一组进行擦除。
* W25Q256JV有8192个可擦除的扇区和512个可擦除的块。
*
*/
/**
* 三个状态寄存器中可写的寄存器位包括:状态寄存器1中的TB、BP[3:0];状态寄存器2中的CMP、LB[3:1]、QE、SRL;状态寄存器3中的DRV1、DRV0、WPS和ADP。
* 所有其他状态寄存器位都是只读的,不会受到写状态寄存器指令的影响。
* LB[3:1]是非易失性的OTP位,一旦被设置为1,就不能被清零。
*/
#define sFLASH_ID 0xef4019 // W25Q256
#define SPI_FLASH_PageSize 256
#define SPI_FLASH_PerWritePageSize 256
#define SPI_FLASH_Secsize 4096
#define W25Q256_WriteEnable 0x06 //写使能命令
#define W25Q256_WriteDisable 0x04 //写屏蔽命令
#define W25Q256_ReadStatusReg1 0x05 //读状态寄存器1命令
#define W25Q256_ReadStatusReg2 0x35 //读状态寄存器2命令
#define W25Q256_ReadStatusReg3 0x15 //读状态寄存器3命令
#define W25Q256_WriteStatusReg1 0x01 //写状态寄存器1命令
#define W25Q256_WriteStatusReg2 0x31 //写状态寄存器2命令
#define W25Q256_WriteStatusReg3 0x11 //写状态寄存器3命令
#define W25Q256_ReadData 0x13 //4字节模式读取数据命令
#define W25Q256_PageProgram 0x02 //页写入命令令(1页256字节)
#define W25Q256_PageProgram_4byte 0x12 //4字节模式页写入命令(1页256字节)
#define W25Q256_SectorErase 0x20 //扇区擦除命令(一次擦除一个扇区/4KB)
#define W25Q256_SectorErase_4byte 0x21 //4字节模式扇区擦除命令(一次擦除一个扇区/4KB)
#define W25Q256_BlockErase_32 0x52 //块擦除命令(一次擦除一个块/32KB)
#define W25Q256_BlockErase_64 0xD8 //块擦除命令(一次擦除一个块/64KB)
#define W25Q256_BlockErase_64_4byte 0xDC //4字节模式块擦除命令(一次擦除一个块/64KB)
#define W25Q256_flashErase 0xC7 //将flash的存储全部擦除命令。可以使用C7/60
#define W25Q256_JedecDeviceID 0x9F //读取设备ID命令
#define W25Q256_Enter4ByteMode 0xB7 //进入4字节地址模式命令
#define WIP_Flag 0x01
uint8_t flash_sec_data[4096]; // flash一个扇区的数据
/**
* @brief 拉低片选线
* @param 无
* @retval 无
*/
void SPI_FLASH_NSS_LOW(void)
{
HAL_GPIO_WritePin(SPI5_NSS_GPIO_Port, SPI5_NSS_Pin, GPIO_PIN_RESET);
}
/**
* @brief 拉高片选线
* @param 无
* @retval 无
*/
void SPI_FLASH_NSS_HIGH(void)
{
HAL_GPIO_WritePin(SPI5_NSS_GPIO_Port, SPI5_NSS_Pin, GPIO_PIN_SET);
}
/**
* @brief 读取 FLASH ID函数
* @param 无
* @retval FLASH ID
*/
uint32_t SPI_FLASH_ReadID(void)
{
uint32_t temp = 0;
uint8_t temp0 = 0;
uint8_t temp1 = 0;
uint8_t temp2 = 0;
// 拉低片选线,开始通信
SPI_FLASH_NSS_LOW();
// 发送获取W25Q256芯片ID指令
SPI_FLASH_SendByte(W25Q256_JedecDeviceID);
// 接收数据
temp0 = SPI_FLASH_ReadByte();
temp1 = SPI_FLASH_ReadByte();
temp2 = SPI_FLASH_ReadByte();
// 拉高片选线,结束通信
SPI_FLASH_NSS_HIGH();
temp = temp0 << 16 | temp1 << 8 | temp2;
return temp;
}
/**
* @brief 发送一个字节
* @param 无
* @retval 无
*/
void SPI_FLASH_SendByte(uint8_t ch)
{
HAL_SPI_Transmit(&hspi5, &ch, 1, 500);
}
/**
* @brief 发送n个字节
* @param pData:发送数据首地址
* @param data_number:发送数据个数(以字节为单位)
* @retval 无
*/
void SPI_FLASH_SendnByte(uint8_t *pData, uint32_t data_number)
{
HAL_SPI_Transmit(&hspi5, pData, data_number, 500);
}
/**
* @brief 读取一个字节
* @param 无
* @retval 接收的数据
*/
uint8_t SPI_FLASH_ReadByte(void)
{
uint8_t rxData = 0;
HAL_SPI_Receive(&hspi5, &rxData, 1, 500);
return rxData;
}
/**
* @brief 接收n个字节
* @param pData:接收数据首地址
* @param data_number:接收数据个数(以字节为单位)
* @retval 无
*/
void SPI_FLASH_ReadnByte(uint8_t *pData, uint32_t data_number)
{
HAL_SPI_Receive(&hspi5, pData, data_number, 500);
}
/**
* @brief 使能写命令
* @param 无
* @param 无
* @retval 无
*/
void SPI_FLASH_WriteEnable(void)
{
// 拉低片选线,开始通信
SPI_FLASH_NSS_LOW();
// 发送写使能指令
SPI_FLASH_SendByte(W25Q256_WriteEnable);
// 拉高片选线,结束通信
SPI_FLASH_NSS_HIGH();
SPI_FLASH_busy_block();
}
/**
* @brief 等待写入、擦除等操作完成
* @param 无
* @param 无
* @retval 无
*/
void SPI_FLASH_WaitForWriteEnd(void)
{
uint8_t FLASH_Status = 0;
// 拉低片选线,开始通信
SPI_FLASH_NSS_LOW();
// 发送写状态寄存器1指令
SPI_FLASH_SendByte(W25Q256_ReadStatusReg1);
do
{
// 获取写状态寄存器1的值并做判断。0:空闲、1:忙碌
FLASH_Status = SPI_FLASH_ReadByte();
} while (SET == (FLASH_Status & WIP_Flag));
// 拉高片选线,结束通信
SPI_FLASH_NSS_HIGH();
}
/**
* @brief 擦除扇区函数
* @param SectorAddr:擦除扇区首地址
* @retval 无
*/
void SPI_FLASH_SectorErase(uint32_t SectorAddr)
{
// 使能写命令
SPI_FLASH_WriteEnable();
// 拉低片选线,开始通信
SPI_FLASH_NSS_LOW();
// 发送擦除扇区命令
SPI_FLASH_SendByte(W25Q256_SectorErase_4byte);
// 发送擦除地址24 ~ 31bit
SPI_FLASH_SendByte((SectorAddr & 0xFF000000) >> 24);
// 发送擦除地址16 ~ 23bit
SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);
// 发送擦除地址8 ~ 15bit
SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);
// 发送擦除地址0 ~ 7bit
SPI_FLASH_SendByte(SectorAddr & 0xFF);
// 拉高片选线,结束通信
SPI_FLASH_NSS_HIGH();
// 等待擦除操作结束
SPI_FLASH_busy_block();
}
/**
* @brief 配置4字节模式函数
* @param 无
* @retval 1:设置成功;
* @retval 0:设置失败;
*/
int SPI_FLASH_FOUR_MODE(void)
{
uint8_t temp = 0;
CODE:
// 拉低片选线,开始通信
SPI_FLASH_NSS_LOW();
// 发送读状态寄存器3命令
SPI_FLASH_SendByte(W25Q256_ReadStatusReg3);
// 读取数据
temp = SPI_FLASH_ReadByte();
// 拉高片选线,结束通信
SPI_FLASH_NSS_HIGH();
if (0x01 & temp)
{
printf("set 4byte mode success\r\n");
return 1;
}
else
{
// 拉低片选线,开始通信
SPI_FLASH_NSS_LOW();
// 发送配置四字节模式指令
SPI_FLASH_SendByte(W25Q256_Enter4ByteMode);
// 拉高片选线,结束通信
SPI_FLASH_NSS_HIGH();
SPI_FLASH_busy_block();
// 使能写命令
SPI_FLASH_WriteEnable();
// 拉低片选线,开始通信
SPI_FLASH_NSS_LOW();
// 发送写状态寄存器3命令
SPI_FLASH_SendByte(W25Q256_WriteStatusReg3);
// 发送要写的数据
SPI_FLASH_SendByte(0x02); // 上电直接进入4byte地址模式
// 拉高片选线,结束通信
SPI_FLASH_NSS_HIGH();
SPI_FLASH_busy_block();
printf("reset 4 byte mode\r\n");
goto CODE;
}
}
/**
* @brief 对 FLASH 按页写入数据,调用本函数写入数据前需要先擦除扇区
* @param pBuffer:要写入数据的指针
* @param WriteAddr:写入数据地址
* @param NumByteToWrite:写入数据长度(以字节为单位)。必须小于等于SPI_FLASH_PerWritePageSize
* @retval 无
*/
void SPI_FLASH_PageWrite(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
// 使能写命令
SPI_FLASH_WriteEnable();
// 拉低片选线,开始通信
SPI_FLASH_NSS_LOW();
// 发送页写入指令
SPI_FLASH_SendByte(W25Q256_PageProgram_4byte);
// 发送写入地址[24,31]bit
SPI_FLASH_SendByte((WriteAddr & 0xFF000000) >> 24);
// 发送写入地址[16,23]bit
SPI_FLASH_SendByte((WriteAddr & 0xFF0000) >> 16);
// 发送写入地址[8,15]bit
SPI_FLASH_SendByte((WriteAddr & 0xFF00) >> 8);
// 发送写入地址[0,7]bit
SPI_FLASH_SendByte(WriteAddr & 0xFF);
for (int i = 0; i < NumByteToWrite; i++)
{
SPI_FLASH_SendByte(pBuffer[i]);
}
// 拉高片选线,结束通信
SPI_FLASH_NSS_HIGH();
SPI_FLASH_busy_block();
}
/**
* @brief 对 FLASH 按扇区写入数据
* @param pBuffer:要写入数据的指针
* @param WriteAddr:写入数据地址
* @param NumByteToWrite:写入数据长度(以字节为单位)。
* @retval 无
*/
void SPI_FLASH_SecWrite(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
uint32_t page_num = 0; // 当前要写入flash的地址属于第几扇区;
uint32_t page_first_addr = 0; // 当前要写入flash的地址所属扇区首地址
uint32_t page_offset_addr = 0; // 要写入的地址相对于当前扇区首地址的偏移
uint8_t *page_data_point = flash_sec_data;
// 确定要写入flash的地址所属页的首地址
page_num = WriteAddr / 4096;
page_first_addr = 4096 * page_num;
page_offset_addr = WriteAddr - page_first_addr;
printf("page_num = %d\r\n",page_num);
printf("page_first_addr = %d\r\n",page_first_addr);
printf("page_offset_addr = %d\r\n",page_offset_addr);
// 读出扇区数据存储到flash_sec_data数组中
SPI_FLASH_BufferRead(page_data_point, page_first_addr, 4096);
// 将要写入的数据赋值到page_data_point中的对应地址
for (int i = page_offset_addr, j = 0; j < NumByteToWrite; i++, j++)
{
page_data_point[i] = pBuffer[j];
}
// 擦除要写入的扇区
SPI_FLASH_SectorErase(page_first_addr);
// 将整理好的数据重新写入到内部FLASH中
for (int i = 0; i < 16; i++)
{
SPI_FLASH_PageWrite(page_data_point, page_first_addr, SPI_FLASH_PageSize);
page_data_point += SPI_FLASH_PageSize;
page_first_addr += SPI_FLASH_PageSize;
}
}
/**
* @brief FALSH不定量数据写入函数,调用本函数写入数据前需要先擦除扇区
* @param pBuffer:要写入数据的指针
* @param WriteAddr:写入数据地址
* @param NumByteToWrite:写入数据长度。(以字节为单位)
* @retval 无
*/
void SPI_FLASH_BufferWrite(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
uint32_t NumOfSec = 0; // 要写多少个扇区
uint32_t NumOfSingle = 0; // 剩下多少不满一个扇区的数据
uint32_t Numofaddrtosec = 0; // 当前写入地址距离扇区结束地址还有多少个字节
if (0 == WriteAddr % 4096)
{
Numofaddrtosec = 0;
}
else
{
Numofaddrtosec = 4096 - WriteAddr % 4096;
if (Numofaddrtosec >= NumByteToWrite)
{
Numofaddrtosec = NumByteToWrite;
}
}
NumOfSec = (NumByteToWrite - Numofaddrtosec) / 4096;
NumOfSingle = (NumByteToWrite - Numofaddrtosec) % 4096;
printf("Numofaddrtosec = %d\r\n",Numofaddrtosec);
printf("NumOfSec = %d\r\n",NumOfSec);
printf("NumOfSingle = %d\r\n",NumOfSingle);
// printf("unwritten_sector_num = %d\r\n",Numofaddrtosec);
if (0 != Numofaddrtosec)
{
SPI_FLASH_SecWrite(pBuffer, WriteAddr, Numofaddrtosec);
WriteAddr += Numofaddrtosec;
pBuffer += Numofaddrtosec;
if (0 != NumOfSec)
{
for (int i = 0; i < NumOfSec; i++)
{
SPI_FLASH_SecWrite(pBuffer, WriteAddr, SPI_FLASH_Secsize);
WriteAddr += SPI_FLASH_Secsize;
pBuffer += SPI_FLASH_Secsize;
}
}
if (0 != NumOfSingle)
{
SPI_FLASH_SecWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
else
{
if (0 != NumOfSec)
{
for (int i = 0; i < NumOfSec; i++)
{
SPI_FLASH_SecWrite(pBuffer, WriteAddr, SPI_FLASH_Secsize);
WriteAddr += SPI_FLASH_Secsize;
pBuffer += SPI_FLASH_Secsize;
}
}
if (0 != NumOfSingle)
{
SPI_FLASH_SecWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
SPI_FLASH_busy_block();
}
/**
* @brief 读取FLASH数据
* @param pBuffer:存储读出数据的指针
* @param WriteAddr:读取地址
* @param NumByteToRead:读取数据长度(以字节为单位)
* @retval 无
*/
void SPI_FLASH_BufferRead(uint8_t *pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
// 拉低片选线,开始通信
SPI_FLASH_NSS_LOW();
// 发送读取数据指令
SPI_FLASH_SendByte(W25Q256_ReadData);
// 发送读取地址[24,31]bit
SPI_FLASH_SendByte((ReadAddr & 0xFF000000) >> 24);
// 发送读取地址[16,23]bit
SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);
// 发送读取地址[8,15]bit
SPI_FLASH_SendByte((ReadAddr & 0xFF00) >> 8);
// 发送读取地址[0,7]bit
SPI_FLASH_SendByte(ReadAddr & 0xFF);
// 读取数据
for (int i = 0; i < NumByteToRead; i++)
{
// 读取一个字节数据
pBuffer[i] = SPI_FLASH_ReadByte();
}
// 拉高片选线,结束通信
SPI_FLASH_NSS_HIGH();
SPI_FLASH_busy_block();
}
/**
* @brief 等待写入、擦除等操作结束
* @param none
* @param none
* @param none
* @retval none
*/
void SPI_FLASH_WaitForOperaEnd(void)
{
uint8_t FLASH_Status = 0;
// 拉低片选线,开始通信
SPI_FLASH_NSS_LOW();
// 发送读状态寄存器1指令
SPI_FLASH_SendByte(W25Q256_ReadStatusReg1);
do
{
// 接收读取状态寄存器1寄存器内容
FLASH_Status = SPI_FLASH_ReadByte();
} while (SET == (FLASH_Status & WIP_Flag));
// 拉高片选线,结束通信
SPI_FLASH_NSS_HIGH();
}
/**
* @brief 忙碌阻塞函数
* @param none
* @param none
* @param none
* @retval none
*/
void SPI_FLASH_busy_block(void)
{
uint8_t FLASH_Status = 0;
while (1)
{
// 拉低片选线,开始通信
SPI_FLASH_NSS_LOW();
// 发送读状态寄存器1指令
SPI_FLASH_SendByte(W25Q256_ReadStatusReg1);
// 接收读取状态寄存器1寄存器内容
FLASH_Status = SPI_FLASH_ReadByte();
// 拉高片选线,结束通信
SPI_FLASH_NSS_HIGH();
if (0 == (1 & FLASH_Status))
{
break;
}
}
}
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_SPI5_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
// 进入4字节地址模式
SPI_FLASH_FOUR_MODE();
printf("READ FLASH ID test!!!\r\n");
device_ID = SPI_FLASH_ReadID();
printf("device_ID = 0x%x\r\n", device_ID);
if (sFLASH_ID == device_ID)
{
printf("FLASH ID read success\r\n");
}
uint8_t ReadBuff[4096];
uint8_t WriteBuff[4096];
uint8_t temp_data[8];
for(int i = 0;i<4096;i++)
{
WriteBuff[i] = 0x77;
}
// for(int i=0;i<256;i++)
// {
// if(0 == i%8 && i >= 8)
// {
// printf("\r\n");
// }
// printf("data1[%04d]=0x%02x\t",i,data1[i]);
// }
printf("\r\n");
SPI_FLASH_BufferRead(ReadBuff, 0, 4096);
for(int i=0;i<4096;i++)
{
if(0 == i%8 && i >= 8)
{
printf("\r\n");
}
printf("ReadBuff[%04d]=0x%02x\t",i,ReadBuff[i]);
}
printf("\r\n");
SPI_FLASH_BufferWrite(WriteBuff, 1, 4096);
SPI_FLASH_BufferRead(ReadBuff, 0, 4096);
for(int i=0;i<4096;i++)
{
if(0 == i%8 && i >= 8)
{
printf("\r\n");
}
printf("ReadBuff[%04d]=0x%02x\t",i,ReadBuff[i]);
}
printf("\r\n");
SPI_FLASH_BufferRead(temp_data, 4095, 8);
for(int i=0;i<8;i++)
{
if(0 == i%8 && i >= 8)
{
printf("\r\n");
}
printf("temp_data[%04d]=0x%02x\t",i,temp_data[i]);
}
printf("\r\n");
char data1[8];
for(int i=0;i<8;i++)
{
data1[i] = i+1;
}
SPI_FLASH_BufferWrite((uint8_t *)data1, 4093, 8);
SPI_FLASH_BufferRead(temp_data, 4093, 8);
for(int i=0;i<8;i++)
{
if(0 == i%8 && i >= 8)
{
printf("\r\n");
}
printf("temp_data[%04d]=0x%02x\t",i,temp_data[i]);
}
printf("\r\n");
int num1 = 222333;
int num2 = 0;
printf("num2 = %d\r\n",num2);
SPI_FLASH_BufferWrite((uint8_t *)&num1, 8190, 4);
SPI_FLASH_BufferRead((uint8_t *)&num2, 8190, 4);
printf("num2 = %d\r\n",num2);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
LED_TIME();
}
/* USER CODE END 3 */
}
6、实验现象
没有问题!