W25Qxx

概述

FLASH

FLASH是一種是非易失性存儲器,即掉電后不會丟失數據,這和RAM(隨機存儲器)不同。

FLASH比起同作用的EEPROM有價格低的優點

FLASH的擦除操作是以扇區為單位的(比起EEPROM來說操作較為不方便)

芯片型號

本文介紹的W25Qxx系列芯片有以下型號

型號容量ID最大地址(最小為0x0)
W25Q162MByte(16Mbit)0xEF140x1FFFFF
W25Q324MByte(32Mbit)0xEF150x3FFFFF
W25Q648MByte(64Mbit)0xEF160x7FFFFF
W25Q12816MByte(128Mbit)0xEF170xFFFFFF
W25Q25632MByte(256Mbit)0xEF180x01FFFFFF
W25Q51264MByte(512Mbit)0xEF190x03FFFFFF

W25Q16、W25Q32、W25Q64、W25Q128的地址為3字節(3x8=24bit)

W25Q256和W25Q512的地址為4字節(4x8=32bit)

本文以W25Q64為例,代碼已經做了兼容處理,可以兼容所有種類芯片

電氣連接

常規SPI協議的W25Qxx

image-20250223144644288

芯片支持的工作電壓 2.7V 到 3.6V,正常工作時電流小于5mA,掉電時低于1uA

注意

1.W25Qxx的封裝和通信協議有許多種,本文以最常見的SOT-8封裝和SPI通信為例

2.FLASH因為物理限制(相比EEPROM省成本),只能將1變成0,所以需要在寫入數據前進行擦除操作

W25Qxx的數據組成

下圖是數據手冊的截圖(W25Q64)

image-20250223145357671

8個數據位(bit)組成1個字節(Byte)

256個字節(Byte)組成1頁(Page),1頁(Page)的數據為256Byte

16頁(Page)組成1個扇區(Sector),1扇區(Sector)的數據為4KByte(16*256B)

16個扇區(Sector)組成1個區塊(Block),1個區塊(Block)數據為64KByte(16*4KB)

不同的芯片有不同的區塊(Block)數量,W25Q64有128個區塊(Block).總容量為8MByte(128*64KByte)

地址的構成如下圖

image-20250223145511876

標準SPI

SPI配置

因為是存儲芯片的驅動,往往需要較大的傳輸速度以支持快速讀取數據

所以使用硬件SPI進行通信

配置內容

1.全雙工主機模式

2.軟件觸發NSS(片選)

3.數據長度8bit

4.先發送MSB(高位)

5.分頻后的時鐘要小于80MHz

6.時鐘極性(CPOL)為低電平有效,時鐘相位(CPHA)為第一個邊沿

7.不啟用CRC校驗

8.不開啟DMA和中斷(DMA傳輸的以后再說)

片選信號

使用軟件觸發需要設置另外的GPIO

必須設置為推挽浮空輸出,最高等級(其他設置會出現問題)

HAL配置

硬件SPI

image-20250223145735205

GPIO

image-20250223151232702

基礎命令

命令介紹

這部分介紹一下W25Qxx的命令構成,這是數據手冊的截圖

image-20250223151727050

使用的是SPI協議,每個通信過程都需要完成一次雙向數據傳輸

也就是寫入命令時會接收到數據,想接收數據時需要寫入命令

上圖的命令有兩種,一種是只需要發送的,一種是即需要發送也需要接收的

兼容性處理

利用一個宏定義和預編譯命令兼容不同芯片的地址長度

需要在頭文件里定義,如

#define W25Q16
#define W25Q32
#define W25Q64
#define W25Q128
#define W25Q256
#define W25Q512

C文件(W25Qxx.c)

#if defined(W25Q16)
uint8_t W25Qxx_Address_Len = 24;
#elif defined(W25Q32)
uint8_t W25Qxx_Address_Len = 24;
#elif defined(W25Q64)
uint8_t W25Qxx_Address_Len = 24;
#elif defined(W25Q128)
uint8_t W25Qxx_Address_Len = 24;
#elif defined(W25Q256)
uint8_t W25Qxx_Address_Len = 32;
#elif defined(W25Q512)
uint8_t W25Qxx_Address_Len = 32;
#else
#error "prese define: W25Q16 or W25Q32 or W25Q64 or W25Q128 or W25Q256 or W25Q512"
#endif

片選和SPI讀寫

這部分是SPI的基礎驅動,包括片選信號的軟件發生和調用HAL庫完成讀寫

SPI的句柄在其他文件里定義,在這個文件中使用需要加這句話

extern SPI_HandleTypeDef hspi1;

頭文件(W25Qxx.h)

extern SPI_HandleTypeDef hspi1;
#define W25Qxx_SPI_Handle hspi1
#define W25Qxx_CS_GPIOx GPIOA
#define W25Qxx_CS_PIN GPIO_PIN_4

C文件(W25Qxx.c)

/*** @brief 設置片選(CS)為低電平選中* @param 無* @retval 無* @author:HZ12138* @date: 2022-07-03 20:49:18*/
void W25Qxx_CS_Low(void)
{HAL_GPIO_WritePin(W25Qxx_CS_GPIOx, W25Qxx_CS_PIN, GPIO_PIN_RESET);
}
/*** @brief 設置片選(CS)為高電平未選中* @param 無* @retval 無* @author:HZ12138* @date: 2022-07-03 20:49:18*/
void W25Qxx_CS_Hight(void)
{HAL_GPIO_WritePin(W25Qxx_CS_GPIOx, W25Qxx_CS_PIN, GPIO_PIN_SET);
}
/*** @brief 通過SPI發送接收數據(阻塞)(SPI是移位發送,接收時要發送數據,發送時也會收到數據)* @param TxData:發送的數據* @retval 接收的數據* @author:HZ12138* @date: 2022-07-03 20:49:18*/
uint8_t W25Qxx_SPI_RW_Byte(uint8_t TxData)
{uint8_t Rxdata;HAL_SPI_TransmitReceive(&W25Qxx_SPI_Handle, &TxData, &Rxdata, 1, 1000);return Rxdata;
}

調用HAL庫的GPIO寫入SPI阻塞傳輸函數即可

讀取ID

數據手冊的截圖

image-20250223152212579

image-20250223152300356

流程

  1. 片選選中(低電平)
  2. 發送讀取ID命令(0x90)
  3. 發送2個字節(Byte)的占位符(任意數據即可)
  4. 發送0x00
  5. 接收2個字節的數據(也需要發送2個字節的占位符)
  6. 片選釋放(高電平)

C文件(W25Qxx.c)

#define W25Qxx_CMD_ID (0x90)             //讀ID
/*** @brief 讀取芯片的ID* @param 無* @retval 芯片的ID* @author:HZ12138* @date: 2022-07-03 20:49:18*/
uint16_t W25Qxx_Read_ID(void)
{uint32_t zj1, zj2;W25Qxx_CS_Low();W25Qxx_SPI_RW_Byte(W25Qxx_CMD_ID);                //發送命令W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder);       //占位符W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder);       //占位符W25Qxx_SPI_RW_Byte(0x00);                         //必須為0zj1 = W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder); //發送占位符讀取數據zj2 = W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder); //發送占位符讀取數據W25Qxx_CS_Hight();return ((zj1 << 8) | zj2);
}

寫使/失能

數據手冊的截圖

image-20250223152350903

image-20250223152429065

image-20250223152448311

寫使能是0x06,寫失能是0x04

調用之后即可一直保持失能/使能狀態

默認狀態是不允許寫入數據(失能),這是為了保護數據,避免誤操作

因此每次上電后想要寫入數據需要使能寫入

建議每次寫入完成后 將寫入失能

為了便于使用,將寫入使能和失能放入同一個函數中

流程

  1. 片選選中(低電平)
  2. 根據輸入選擇命令發送
  3. 片選釋放(高電平)

C文件(W25Qxx.c)

#define W25Qxx_CMD_Write_Enable (0x06)   //寫功能打開
#define W25Qxx_CMD_Write_Disable (0x04)  //寫功能關閉
/*** @brief 寫保護* @param Functional:* @arg 1:     允許寫入* @arg 0:     不允許寫入* @retval 無* @author:HZ12138* @date: 2022-07-03 20:49:18*/
void W25Qxx_Write_Protect(uint8_t Functional)
{W25Qxx_CS_Low();if (Functional == 0)W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Write_Disable);//不允許寫入else if (Functional == 1)W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Write_Enable);//允許寫入W25Qxx_CS_Hight();
}

讀寄存器1

數據手冊的關于寄存器1的截圖,用到時再細說各個位的功能

image-20250223153638550

讀狀態寄存器1的命令 和時序

image-20250223153720929

image-20250223153739647

流程

  1. 片選選中(低電平)
  2. 發送命令并接收數據
  3. 片選釋放(高電平)

C文件(W25Qxx.c)

#define W25Qxx_CMD_ReadStatusReg1 (0x05) //讀取狀態寄存器
/*** @brief 讀取寄存器1的狀態* @param 無* @retval 狀態* @author:HZ12138* @date: 2022-07-03 20:49:18*/
uint8_t W25Qxx_Read_StatusReg1(void)
{uint8_t zj = 0;W25Qxx_CS_Low();W25Qxx_SPI_RW_Byte(W25Qxx_CMD_ReadStatusReg1);   //發送命令zj = W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder); //接收數據W25Qxx_CS_Hight();return zj;
}

等待寫入完成

讀取狀態寄存器1的最低位

為1則寫忙碌

為0則為空閑

利用一個死循環來確保已經寫入已經完成

/*** @brief 等待寫入/擦除完成* @param 無* @retval 無* @author:HZ12138* @date: 2022-07-03 20:49:18*/
void W25Qxx_Wait_Free(void)
{while (W25Qxx_Read_StatusReg1() & 0x01) //等待寫完;
}

扇區擦除

上文提到過,FLASH的擦除最小單位為扇區

image-20250223153837852

image-20250223153856297

流程

  1. 片選選中(低電平)
  2. 發送命令
  3. 發送地址(只要是扇區內的任意地址即可)
  4. 片選釋放(高電平)

注意:

  1. 地址長度需要根據不同芯片來定(24bit或32bit)
  2. 地址是扇區內的任意一個地址即可

C文件(W25Qxx.c)

/*** @brief 扇區擦除* @param Address:要擦除的扇區內的任意地址* @retval 無* @author:HZ12138* @date: 2022-07-03 20:49:18*/
void W25Qxx_Sector_Erase(uint32_t Address)
{Address &= 0xFFFFF000;W25Qxx_Write_Protect(1); //允許寫入W25Qxx_CS_Low();W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Sector_Erase); //命令if (W25Qxx_Address_Len == 32)W25Qxx_SPI_RW_Byte((Address & 0xFF000000) >> 24); //如果是32位的地址則發送W25Qxx_SPI_RW_Byte((Address & 0x00FF0000) >> 16); //地址W25Qxx_SPI_RW_Byte((Address & 0x0000FF00) >> 8);  //地址W25Qxx_SPI_RW_Byte((Address & 0x000000FF) >> 0);  //地址W25Qxx_CS_Hight();W25Qxx_Write_Protect(0); //關閉寫入// while (W25Qxx_Read_StatusReg1() & 0x01) //等待寫完//     ;
}

讀取數據

讀取數據的命令

image-20250223153926926

image-20250223154039993

流程

  1. 片選選中(低電平)
  2. 發送命令
  3. 發送起始地址
  4. 接收數據
  5. 片選釋放(高電平)

注意

  1. 地址為起始地址
  2. 地址會自增
  3. 可以跨頁,區塊,扇區

設計為輸入起始地址,將數據放到的數字的地址,長度

將讀取的數據放入指定的位置

C文件(W25Qxx.c)

#define W25Qxx_CMD_Read_Data (0x03)      //讀取數據
/*** @brief 讀取數據* @param Address:要讀取的地址* @param Buf:將數據放入的數組地址* @param Len:讀取的字節數* @retval 無* @author:HZ12138* @date: 2022-07-03 20:49:18*/
void W25Qxx_Read_Data(uint32_t Address, uint8_t *Buf, uint32_t Len)
{W25Qxx_CS_Low();W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Read_Data); //命令if (W25Qxx_Address_Len == 32)W25Qxx_SPI_RW_Byte((Address & 0xFF000000) >> 24); //如果是32位的地址則發送W25Qxx_SPI_RW_Byte((Address & 0x00FF0000) >> 16); //地址W25Qxx_SPI_RW_Byte((Address & 0x0000FF00) >> 8);  //地址W25Qxx_SPI_RW_Byte((Address & 0x000000FF) >> 0);  //地址for (int i = 0; i < Len; i++)Buf[i] = W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder);W25Qxx_CS_Hight();
}

按頁寫入數據

本函數是為了之后的數據寫入做的準備函數

image-20250223154105119

image-20250223154125754

流程

  1. 片選選中(低電平)
  2. 發送命令
  3. 發送地址
  4. 發送數據
  5. 片選釋放(高電平)

注意:

  1. 本命令不可以跨頁使用,如果發送的數據長度小于本頁剩余長度,則會從本頁最開頭覆蓋數據
  2. FLASH只能從數據只能1->0,寫入之前需要確保數據為0xFF,避免出現問題

設計:

輸入要寫入的地址和緩沖區及長度,即可寫入指定頁的全部數據

內部已經做了處理,保證寫入的數據從頁的頭部開始

本函數是以后工具函數

C文件(W25Qxx.c)

#define W25Qxx_CMD_Write_Page (0x02)     //寫頁
/*** @brief 按頁寫入數據* @param Address:要寫入的地址的首地址(必須首地址)* @param Buf:將數據的數組地址* @param Len:寫入的字節數* @retval 無* @author:HZ12138* @date: 2022-07-03 20:49:18*/
void W25Qxx_Write_Page(uint32_t Address, uint8_t *Buf, uint32_t Len)
{W25Qxx_Write_Protect(1); //允許寫入W25Qxx_Wait_Free();W25Qxx_CS_Low();W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Write_Page); //命令if (W25Qxx_Address_Len == 32)W25Qxx_SPI_RW_Byte((Address & 0xFF000000) >> 24); //如果是32位的地址則發送W25Qxx_SPI_RW_Byte((Address & 0x00FF0000) >> 16); //地址W25Qxx_SPI_RW_Byte((Address & 0x0000FF00) >> 8);  //地址W25Qxx_SPI_RW_Byte((Address & 0x00000000) >> 0);  //地址(確保是首地址)for (int i = 0; i < 256; i++){if (i < Len)W25Qxx_SPI_RW_Byte(Buf[i]);elseW25Qxx_SPI_RW_Byte(0xFF);}W25Qxx_CS_Hight();W25Qxx_Write_Protect(0); //關閉寫入
}

拓展命令

讀取扇區數據

之前說過重定向printf到串口,本部分將用到這個

本函數的功能為:讀取整個扇區的數據并通過串口發送到上位機顯示

思路就是調用之前讀數據命令,并將其放入一個緩沖區內,再進行發送(可以用DMA)

for (uint32_t i = 0; i < 16; i++)W25Qxx_Read_Data((Address & 0xFFFFF000) | (i << 8), W25Qxx_Buf[i], 256);

上面這段代碼即為核心

進行一個循環,遍歷整個扇區內的頁(0x0-0xF)

獲取區塊和扇區編號,加上每頁的編號 獲取地址(Address & 0xFFFFF000) | (i << 8)

將緩沖區的數組首地址送入,固定數據長度256,即可讀取

C文件(W25Qxx.c)

uint8_t W25Qxx_Buf[16][256] = {0};
/*** @brief 打印出整個扇區的數據* @param Address:扇區內的任意地址* @return 無* @author HZ12138* @date 2022-07-04 10:13:03*/
void W25Qxx_Print_Sector(uint32_t Address)
{for (uint32_t i = 0; i < 16; i++)W25Qxx_Read_Data((Address & 0xFFFFF000) | (i << 8), W25Qxx_Buf[i], 256);for (int i = 0; i < 16; i++){printf("%06X:", (Address & 0xFFFFF000) | (i << 8));for (int j = 0; j < 256; j++){printf("%02X ", W25Qxx_Buf[i][j]);}printf("\r\n");}
}

扇區單位寫入數據

上文說過,芯片擦除數據的最小單位為扇區

因此進行數據寫入時,會將整個扇區的數據都給擦除,我們需要建立一個緩沖區來存儲數據,才能精確更改幾個數據

流程:

  1. 讀取這個扇區原來的數據到緩沖區
  2. 根據需要寫入數據到緩沖區
  3. 擦除這個扇區的數據
  4. 將緩沖區的數據寫回芯片

用到了一些位操作,建議配合前文的地址構成理解

讀取和寫入的地址計算和上文的讀取扇區數據一樣, (Add_Bef & 0xFFFFF000) | (i << 8)

這邊重點看緩沖區的地址計算,即這段

 for (uint32_t i = Add_Bef; i < Add_Aft; i++) //向緩沖區寫入數據{W25Qxx_Buf[(i & 0x00000F00) >> 8][i & 0x000000FF] = Buf[Num];Num++;}

我們計算一下數據寫完后的地址(長度與初始地址求和即可)

然后進行一個循環,遍歷這段地址

取出頁編號(i & 0x00000F00) >> 8)

取出頁內的數據編號(i & 0x000000FF)

向緩沖區內寫入即可

另外仔細看一下這個緩沖區

uint8_t W25Qxx_Buf[16][256] = {0};

第一維代表扇區內的頁,第二維代表頁內的數據編號

C文件(W25Qxx.c)

uint8_t W25Qxx_Buf[16][256] = {0};
/*** @brief 向扇區寫入數據* @param Address:要寫入的地址* @param Buf:寫入數據的數組地址* @param Len:長度* @return 無* @author:HZ12138* @date: 2022-07-03 20:49:18*/
void W25Qxx_Write_Sector(uint32_t Address, uint8_t *Buf, uint32_t Len)
{uint32_t Add_Bef = Address;uint32_t Add_Aft = Address + Len;uint32_t Num = 0;for (uint32_t i = 0; i < 16; i++) //讀取原來數據到緩沖區W25Qxx_Read_Data((Add_Bef & 0xFFFFF000) | (i << 8), W25Qxx_Buf[i], 256);for (uint32_t i = Add_Bef; i < Add_Aft; i++) //向緩沖區寫入數據{W25Qxx_Buf[(i & 0x00000F00) >> 8][i & 0x000000FF] = Buf[Num];Num++;}W25Qxx_Sector_Erase(Add_Bef); //清空這個扇區W25Qxx_Wait_Free();for (uint32_t i = 0; i < 16; i++) //向FLASH寫入緩沖區內的數據{W25Qxx_Write_Page((Add_Bef & 0xFFFFF000) | (i << 8), W25Qxx_Buf[i], 256);W25Qxx_Wait_Free();}
}

寫入任意長度數據

這個函數即為寫入函數最終的成品,可以無視扇區限制寫入數據

思路:將要寫入的數據根據扇區的種類分為3部分,第一個扇區,中間扇區,最后一個扇區

  1. 第一個扇區的數據長度不定,數據長度為(本扇區結束地址-起始地址)
  2. 中間扇區數據長度確定,數據長度為4096
  3. 最后一個扇區數據長度不定,數據長度為(結束地址-本扇區起始地址)
  4. 只需要根據計數以此推進輸入數據再調用寫入扇區的函數即可

這個是第一個扇區的數據寫入

W25Qxx_Write_Sector(Add_Bef, Buf, ((Add_Bef & 0xFFFFF000) + 0x00001000 - Add_Bef));

將起始地址的區塊扇區編號提取出來再加1個扇區的地址長度0x1000,結果為下一個扇區的起始地址,與數據寫入的起始地址做差即可得到數據長度

中間扇區部分為固定的長度4096

只需要進行一個循環,從起始扇區的下一個扇區開始,到倒數第二個扇區,更新輸入數組的開始地址,更新計數值即可

for (uint32_t i = (Add_Bef & 0xFFFFF000) + 0x00001000;i < (Add_Aft & 0xFFFFF000); i += 0x00001000){W25Qxx_Write_Sector(i, &Buf[Num], 4096);Num += 4096;}

最后一個扇區的長度不定

W25Qxx_Write_Sector((Add_Aft & 0xFFFFF000), &Buf[Num], Add_Aft - (Add_Aft & 0xFFFFF000));

數據長度為結束地址減區本扇區的起始地址:

Add_Aft - (Add_Aft & 0xFFFFF000)

C文件(W25Qxx.c)

/*** @brief 寫入數據* @param Address:要寫入的地址* @param Buf:寫入數據的數組地址* @param Len:長度* @return 無* @date 2022-07-04 21:50:38*/
void W25Qxx_Write(uint32_t Address, uint8_t *Buf, uint32_t Len)
{uint32_t Add_Bef = Address;uint32_t Add_Aft = Address + Len;uint32_t Num = 0;W25Qxx_Write_Sector(Add_Bef, Buf, ((Add_Bef & 0xFFFFF000) + 0x00001000 - Add_Bef));Num = ((Add_Bef & 0xFFFFF000) + 0x00001000 - Add_Bef);for (uint32_t i = (Add_Bef & 0xFFFFF000) + 0x00001000;i < (Add_Aft & 0xFFFFF000); i += 0x00001000){W25Qxx_Write_Sector(i, &Buf[Num], 4096);Num += 4096;}W25Qxx_Write_Sector((Add_Aft & 0xFFFFF000), &Buf[Num], Add_Aft - (Add_Aft & 0xFFFFF000));
}

QSPI

QSPI一般用于可以映射的芯片,用外置FLASH存儲代碼文件

例如STM32H750等

設映射到地址Addr 當芯片讀取Addr 的代碼時就會自動調用QSPI總線訪問FLASH用來擴大存儲空間

QSPI配置

image-20250318230521015

開啟4線SPI模式

時鐘根據情況設置 一般W25qxx時鐘要小于120MHz

先進先出緩沖區(Fifo Threshold) 設為 4

采樣位置(Sample Shifting Half Cycle) 設為 半時鐘采樣( Sample Shifting Half Cycle)

存儲大小(Flash Size) 根據芯片設置 是這里的數是 2 x 2^x 2x (eg: W25Q64 是64Mbit=8MByte log ? 2 ( 8 × 1024 × 1024 ) + 1 = 23 + 1 = 24 \log_{2}{(8\times 1024\times 1024)} +1 =23+1=24 log2?(8×1024×1024)+1=23+1=24 ) 注意最后要+1 來解決最后字節問題

片選高延遲 (Chip Select High Time ) 兩次命令間的延遲周期 一般選2-5即可

其余默認即可

image-20250318232740991

注意GPIO中要選擇最高模式

命令

HAL庫API

QSPI命令結構體
類型名稱功能
uint32_tInstruction命令(8bits)
uint32_tAddress地址(32bits)
uint32_tAlternateBytes備用字節(32bits)
uint32_tAddressSizeQSPI_ADDRESS_8_BITSQSPI_ADDRESS_32_BITS
uint32_tAlternateBytesSize交換字節大小(未用到)
uint32_tDummyCycles空周期(0-31)
uint32_tInstructionMode指令模式(QSPI_INSTRUCTION_NONE等)
uint32_tAddressMode地址模式(QSPI_ADDRESS_NONE等)
uint32_tAlternateByteMode字節交換模式(QSPI_ALTERNATE_BYTES_NONE
uint32_tDataMode數據模式(QSPI_DATA_NONE等)
uint32_tNbData數據長度(32bits)(0表示不確定的長度直到內存結束)
uint32_tDdrModeDDR模式(一般關閉即可QSPI_DDR_MODE_DISABLE)
uint32_tDdrHoldHalfCycleddr保持周期(一般QSPI_DDR_HHC_ANALOG_DELAY)
uint32_tSIOOMode是否單獨命令(一般每次都發命令QSPI_SIOO_INST_EVERY_CMD)

QSPI內存映射結構體

類型名稱功能
uint32_tTimeOutPeriodFIIFO滿等待數(16bits)
uint32_tTimeOutActivation是否使用超時(QSPI_TIMEOUT_COUNTER_DISABLE)
命令發送
類型名稱功能
QSPI_HandleTypeDefhqspi句柄
QSPI_CommandTypeDefcmd控制結構體
uint32_tTimeout超時
HAL_StatusTypeDef返回狀態
HAL_StatusTypeDef HAL_QSPI_Command(QSPI_HandleTypeDef *hqspi, QSPI_CommandTypeDef *cmd, uint32_t Timeout)
獲取數據
類型名稱功能
QSPI_HandleTypeDefhqspi句柄
uint8_tpData數據緩沖區
uint32_tTimeout超時
HAL_StatusTypeDef返回狀態
HAL_StatusTypeDef HAL_QSPI_Receive(QSPI_HandleTypeDef *hqspi, uint8_t *pData, uint32_t Timeout)
進入內存映射
類型名稱功能
QSPI_HandleTypeDefhqspi句柄
QSPI_CommandTypeDefcmd命令結構體
QSPI_MemoryMappedTypeDefcfg內存映射結構體
HAL_StatusTypeDef返回狀態
HAL_StatusTypeDef HAL_QSPI_MemoryMapped(QSPI_HandleTypeDef *hqspi, QSPI_CommandTypeDef *cmd, QSPI_MemoryMappedTypeDef *cfg)

宏定義

#define W25Qxx_CMD_Write_Enable (0x06)   // 寫功能打開
#define W25Qxx_CMD_Write_Disable (0x04)  // 寫功能關閉
#define W25Qxx_CMD_Device_ID (0x90)      // 讀設備ID
#define W25Qxx_CMD_ReadStatusReg1 (0x05) // 讀取狀態寄存器1
#define W25Qxx_CMD_ReadStatusReg2 (0x35) // 讀取狀態寄存器2
#define W25Qxx_CMD_ReadStatusReg3 (0x35) // 讀取狀態寄存器3
#define W25Qxx_CMD_Placeholder (0xFF)    // 占位符
#define W25Qxx_CMD_Sector_Erase (0x20)   // 扇區擦除
#define W25Qxx_CMD_Read_Data (0x03)      // 讀取數據
#define W25Qxx_CMD_Write_Page (0x02)     // 寫頁#define W25Qxx_CMD_QSPI_Read (0x0B)  // QSPI讀
#define W25Qxx_CMD_Enter_QSPI (0x38) // 進入QSPI模式
#define W25Qxx_CMD_ENABLE_RESET (0x66)
#define W25Qxx_CMD_RESET (0x99)
#define W25Qxx_CMD_QSPI_FAST_READ (0xEB)uint8_t W25Qxx_Buf[16][256] = {0};#if defined(W25Q16)
uint8_t W25Qxx_Address_Len = 24;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_24_BITS
#elif defined(W25Q32)
uint8_t W25Qxx_Address_Len = 24;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_24_BITS#elif defined(W25Q64)
uint8_t W25Qxx_Address_Len = 24;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_24_BITS#elif defined(W25Q128)
uint8_t W25Qxx_Address_Len = 24;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_24_BITS#elif defined(W25Q256)
uint8_t W25Qxx_Address_Len = 32;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_32_BITS#elif defined(W25Q512)
uint8_t W25Qxx_Address_Len = 32;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_32_BITS#else
#error "prese define: W25Q16 or W25Q32 or W25Q64 or W25Q128 or W25Q256 or W25Q512"
#endif

發送命令

封裝一個QSPI發送命令的函數,具體看注釋

/*** @brief QSPI發送命令* @param cmd 命令* @param addr 地址* @param InstructionMode 指令模式* @param AddressMode 地址模式* @param AddressSize 地址長度* @param DataMode 數據模式* @param SIOOMode 僅發一次指令* @param dmcycle 空周期* @param NbData 數據長度* @param buf 讀取緩沖區* @author HZ12138* @date 2025-03-19 09:15:00*/
void W25Qxx_QSPI_CMD(uint8_t cmd, uint32_t addr, uint32_t InstructionMode, uint32_t AddressMode,uint32_t AddressSize, uint32_t DataMode, uint32_t SIOOMode, uint8_t dmcycle, uint32_t NbData, uint8_t *buf)
{QSPI_CommandTypeDef Cmdhandler;Cmdhandler.Instruction = cmd;     // 指令Cmdhandler.Address = addr;        // 地址Cmdhandler.DummyCycles = dmcycle; // 設置空指令周期數Cmdhandler.InstructionMode = InstructionMode; // 指令模式Cmdhandler.AddressMode = AddressMode;         // 地址模式Cmdhandler.AddressSize = AddressSize;         // 地址長度Cmdhandler.DataMode = DataMode;               // 數據模式// QSPI_SIOO_INST_EVERY_CMDCmdhandler.SIOOMode = SIOOMode;Cmdhandler.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; // 無交替字節Cmdhandler.DdrMode = QSPI_DDR_MODE_DISABLE;               // 關閉DDR模式Cmdhandler.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;Cmdhandler.NbData = NbData; // 數據長度HAL_QSPI_Command(&W25Qxx_QSPI_Handle, &Cmdhandler, 5000);if (NbData != 0 && buf != NULL)HAL_QSPI_Receive(&W25Qxx_QSPI_Handle, buf, 5000);
}

讀取ID

uint16_t W25Qxx_Read_ID(void)
{uint8_t temp[2];uint16_t deviceid;W25Qxx_QSPI_CMD(W25Qxx_CMD_Device_ID, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_EVERY_CMD, 0, 2, temp);deviceid = (temp[0] << 8) | temp[1];return deviceid;
}

讀取數據

void W25Qxx_Read_Data(uint32_t Address, uint8_t *Buf, uint32_t Len)
{W25Qxx_QSPI_CMD(W25Qxx_CMD_QSPI_Read, Address, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_EVERY_CMD, 2, Len, Buf);}

讀取狀態寄存器

uint8_t W25Qxx_Read_StatusReg(uint8_t num)
{uint8_t temp;switch (num){case 1:W25Qxx_QSPI_CMD(W25Qxx_CMD_ReadStatusReg1, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_EVERY_CMD, 0, 1, &temp);break;case 2:W25Qxx_QSPI_CMD(W25Qxx_CMD_ReadStatusReg2, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_EVERY_CMD, 0, 1, &temp);break;case 3:W25Qxx_QSPI_CMD(W25Qxx_CMD_ReadStatusReg3, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_EVERY_CMD, 0, 1, &temp);break;default:break;}return temp;}

等待寫入完成

void W25Qxx_Wait_Free(void)
{while (W25Qxx_Read_StatusReg(1) & 0x01) // 等待寫完;
}

扇區擦除

void W25Qxx_Sector_Erase(uint32_t Address)
{W25Qxx_Write_Protect(1);W25Qxx_QSPI_CMD(W25Qxx_CMD_Sector_Erase, Address, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_NONE, QSPI_SIOO_INST_EVERY_CMD, 0, 0, NULL);W25Qxx_Wait_Free();W25Qxx_Write_Protect(0);
}

復位

void W25Qxx_reset(void)
{W25Qxx_QSPI_CMD(W25Qxx_CMD_ENABLE_RESET, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE, QSPI_SIOO_INST_EVERY_CMD, 0, 0, NULL);HAL_Delay(5);W25Qxx_QSPI_CMD(W25Qxx_CMD_RESET, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE, QSPI_SIOO_INST_EVERY_CMD, 0, 0, NULL);HAL_Delay(10);
}

初始化

void W25Qxx_init(void)
{W25Qxx_reset();W25Qxx_QSPI_CMD(W25Qxx_CMD_Enter_QSPI, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE, QSPI_SIOO_INST_EVERY_CMD, 0, 0, NULL);
}

頁寫入

void W25Qxx_Write_Page(uint32_t Address, uint8_t *Buf, uint32_t Len)
{W25Qxx_Write_Protect(1);W25Qxx_QSPI_CMD(W25Qxx_CMD_Write_Page, Address, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_ONLY_FIRST_CMD, 0, Len, NULL);HAL_QSPI_Transmit(&W25Qxx_QSPI_Handle, Buf, 5000);W25Qxx_Wait_Free();W25Qxx_Write_Protect(0);
}

內存映射

調用函數后會將 0x90000000的內存映射到FLASH里

void W25Qxx_Enter_MemoryMappedMode(void)
{QSPI_CommandTypeDef s_command = {0};QSPI_MemoryMappedTypeDef s_mem_mapped_cfg = {0};s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;     // 1線方式發送指令s_command.AddressSize = W25Qxx_QSPI_ADDR_SIZE;           // 32位地址s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; // 無交替字節s_command.DdrMode = QSPI_DDR_MODE_DISABLE;               // W25Q256JV不支持DDRs_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;  // DDR模式,數據輸出延遲s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;           // 每次傳輸都發指令s_command.Instruction = W25Qxx_CMD_QSPI_FAST_READ; // 快速讀取命令s_command.AddressMode = QSPI_ADDRESS_4_LINES;      // 4地址線s_command.DataMode = QSPI_DATA_4_LINES;            // 4數據線s_command.DummyCycles = 6;                         // 空周期s_mem_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;s_mem_mapped_cfg.TimeOutPeriod = 0;HAL_QSPI_MemoryMapped(&W25Qxx_QSPI_Handle, &s_command, &s_mem_mapped_cfg);
}

源碼

W25Qxx.h

#ifndef _W25Qxx_H
#define _W25Qxx_H
#include "main.h"
#include "Print.h"
/*
需要:
SSPI:1.GPIO推挽浮空輸出,最高等級2.硬件SPI全雙工主機(默認設置)片選信號由軟件觸發數據長8bit先發高位MSB時鐘分頻后<80MHz上升沿時鐘單周期觸發
QSPI:預分頻 1FIFO 4半時鐘時采集片選延遲5周期其余默認
*/
#define W25Q64// #define W25Qxx_SSPI // 使用標準SPI
#define W25Qxx_QSPI // 使用QSPI#ifdef W25Qxx_SSPI
extern SPI_HandleTypeDef hspi2;
#define W25Qxx_SPI_Handle hspi2
#define W25Qxx_CS_GPIOx GPIOB
#define W25Qxx_CS_PIN GPIO_PIN_12
#endif#ifdef W25Qxx_QSPI
extern QSPI_HandleTypeDef hqspi;
#define W25Qxx_QSPI_Handle hqspi
#endifuint16_t W25Qxx_Read_ID(void);
void W25Qxx_Read_Data(uint32_t Address, uint8_t *Buf, uint32_t Len);
void W25Qxx_Write_Protect(uint8_t Functional);
void W25Qxx_Sector_Erase(uint32_t Address);
void W25Qxx_init(void);
void W25Qxx_reset(void);
void W25Qxx_Wait_Free(void);
void W25Qxx_Write_Sector(uint32_t Address, uint8_t *Buf, uint32_t Len);
void W25Qxx_Print_Sector(uint32_t Address);
void W25Qxx_Write(uint32_t Address, uint8_t *Buf, uint32_t Len);
void W25Qxx_Enter_MemoryMappedMode(void);
void W25Qxx_Write_Page(uint32_t Address, uint8_t *Buf, uint32_t Len);#endif

W25Qxx.c

#include "W25Qxx.h"#define W25Qxx_CMD_Write_Enable (0x06)   // 寫功能打開
#define W25Qxx_CMD_Write_Disable (0x04)  // 寫功能關閉
#define W25Qxx_CMD_Device_ID (0x90)      // 讀設備ID
#define W25Qxx_CMD_ReadStatusReg1 (0x05) // 讀取狀態寄存器1
#define W25Qxx_CMD_ReadStatusReg2 (0x35) // 讀取狀態寄存器2
#define W25Qxx_CMD_ReadStatusReg3 (0x35) // 讀取狀態寄存器3
#define W25Qxx_CMD_Placeholder (0xFF)    // 占位符
#define W25Qxx_CMD_Sector_Erase (0x20)   // 扇區擦除
#define W25Qxx_CMD_Read_Data (0x03)      // 讀取數據
#define W25Qxx_CMD_Write_Page (0x02)     // 寫頁#define W25Qxx_CMD_QSPI_Read (0x0B)  // QSPI讀
#define W25Qxx_CMD_Enter_QSPI (0x38) // 進入QSPI模式
#define W25Qxx_CMD_ENABLE_RESET (0x66)
#define W25Qxx_CMD_RESET (0x99)
#define W25Qxx_CMD_QSPI_FAST_READ (0xEB)uint8_t W25Qxx_Buf[16][256] = {0};#if defined(W25Q16)
uint8_t W25Qxx_Address_Len = 24;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_24_BITS
#elif defined(W25Q32)
uint8_t W25Qxx_Address_Len = 24;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_24_BITS#elif defined(W25Q64)
uint8_t W25Qxx_Address_Len = 24;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_24_BITS#elif defined(W25Q128)
uint8_t W25Qxx_Address_Len = 24;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_24_BITS#elif defined(W25Q256)
uint8_t W25Qxx_Address_Len = 32;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_32_BITS#elif defined(W25Q512)
uint8_t W25Qxx_Address_Len = 32;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_32_BITS#else
#error "prese define: W25Q16 or W25Q32 or W25Q64 or W25Q128 or W25Q256 or W25Q512"
#endif#ifdef W25Qxx_SSPI
/*** @brief 設置片選(CS)為低電平選中* @param 無* @retval 無* @author:HZ12138* @date: 2022-07-03 20:49:18*/
void W25Qxx_CS_Low(void)
{HAL_GPIO_WritePin(W25Qxx_CS_GPIOx, W25Qxx_CS_PIN, GPIO_PIN_RESET);
}
/*** @brief 設置片選(CS)為高電平未選中* @param 無* @retval 無* @author:HZ12138* @date: 2022-07-03 20:49:18*/
void W25Qxx_CS_Hight(void)
{HAL_GPIO_WritePin(W25Qxx_CS_GPIOx, W25Qxx_CS_PIN, GPIO_PIN_SET);
}
/*** @brief 通過SPI發送接收數據(阻塞)(SPI是移位發送,接收時要發送數據,發送時也會收到數據)* @param TxData:發送的數據* @retval 接收的數據* @author:HZ12138* @date: 2022-07-03 20:49:18*/
uint8_t W25Qxx_SPI_RW_Byte(uint8_t TxData)
{uint8_t Rxdata;HAL_SPI_TransmitReceive(&W25Qxx_SPI_Handle, &TxData, &Rxdata, 1, 1000);return Rxdata;
}/*** @brief 批量讀SPI* @param RX_Buf:接收緩沖區* @param len:接收長度* @author HZ12138* @date 2025-02-23 22:58:29*/
void W25Qxx_SPI_Read_Len(uint8_t *RX_Buf, uint32_t len)
{HAL_SPI_Receive(&W25Qxx_SPI_Handle, RX_Buf, len, 1000);
}
/*** @brief 批量寫SPI* @param TX_Buf:發送緩沖區* @param len:發送長度* @author HZ12138* @date 2025-02-23 23:00:49*/
void W25Qxx_SPI_Write_Len(uint8_t *TX_Buf, uint32_t len)
{HAL_SPI_Transmit(&W25Qxx_SPI_Handle, TX_Buf, len, 1000);
}
#endif/*** @brief 向扇區寫入數據* @param Address:要寫入的地址* @param Buf:寫入數據的數組地址* @param Len:長度* @return 無* @author:HZ12138* @date: 2022-07-03 20:49:18*/
void W25Qxx_Write_Sector(uint32_t Address, uint8_t *Buf, uint32_t Len)
{uint32_t Add_Bef = Address;uint32_t Add_Aft = Address + Len;uint32_t Num = 0;for (uint32_t i = 0; i < 16; i++) // 讀取原來數據到緩沖區W25Qxx_Read_Data((Add_Bef & 0xFFFFF000) | (i << 8), W25Qxx_Buf[i], 256);for (uint32_t i = Add_Bef; i < Add_Aft; i++) // 向緩沖區寫入數據{W25Qxx_Buf[(i & 0x00000F00) >> 8][i & 0x000000FF] = Buf[Num];Num++;}W25Qxx_Sector_Erase(Add_Bef); // 清空這個扇區W25Qxx_Wait_Free();for (uint32_t i = 0; i < 16; i++) // 向FLASH寫入緩沖區內的數據{W25Qxx_Write_Page((Add_Bef & 0xFFFFF000) | (i << 8), W25Qxx_Buf[i], 256);W25Qxx_Wait_Free();}
}
/*** @brief 打印出整個扇區的數據* @param Address:扇區內的任意地址* @return 無* @author HZ12138* @date 2022-07-04 10:13:03*/
void W25Qxx_Print_Sector(uint32_t Address)
{for (uint32_t i = 0; i < 16; i++)W25Qxx_Read_Data((Address & 0xFFFFF000) | (i << 8), W25Qxx_Buf[i], 256);for (int i = 0; i < 16; i++){printf("%06X:", (Address & 0xFFFFF000) | (i << 8));for (int j = 0; j < 256; j++){printf("%02X ", W25Qxx_Buf[i][j]);}printf("\r\n");}
}
/*** @brief 寫入數據* @param Address:要寫入的地址* @param Buf:寫入數據的數組地址* @param Len:長度* @return 無* @date 2022-07-04 21:50:38*/
void W25Qxx_Write(uint32_t Address, uint8_t *Buf, uint32_t Len)
{uint32_t Add_Bef = Address;uint32_t Add_Aft = Address + Len;uint32_t Num = 0;W25Qxx_Write_Sector(Add_Bef, Buf, ((Add_Bef & 0xFFFFF000) + 0x00001000 - Add_Bef));Num = ((Add_Bef & 0xFFFFF000) + 0x00001000 - Add_Bef);for (uint32_t i = (Add_Bef & 0xFFFFF000) + 0x00001000;i < (Add_Aft & 0xFFFFF000); i += 0x00001000){W25Qxx_Write_Sector(i, &Buf[Num], 4096);Num += 4096;}W25Qxx_Write_Sector((Add_Aft & 0xFFFFF000), &Buf[Num], Add_Aft - (Add_Aft & 0xFFFFF000));
}#ifdef W25Qxx_QSPI
/*** @brief QSPI發送命令* @param cmd 命令* @param addr 地址* @param InstructionMode 指令模式* @param AddressMode 地址模式* @param AddressSize 地址長度* @param DataMode 數據模式* @param SIOOMode 僅發一次指令* @param dmcycle 空周期* @param NbData 數據長度* @param buf 讀取緩沖區* @author HZ12138* @date 2025-03-19 09:15:00*/
void W25Qxx_QSPI_CMD(uint8_t cmd, uint32_t addr, uint32_t InstructionMode, uint32_t AddressMode,uint32_t AddressSize, uint32_t DataMode, uint32_t SIOOMode, uint8_t dmcycle, uint32_t NbData, uint8_t *buf)
{QSPI_CommandTypeDef Cmdhandler;Cmdhandler.Instruction = cmd;     // 指令Cmdhandler.Address = addr;        // 地址Cmdhandler.DummyCycles = dmcycle; // 設置空指令周期數Cmdhandler.InstructionMode = InstructionMode; // 指令模式Cmdhandler.AddressMode = AddressMode;         // 地址模式Cmdhandler.AddressSize = AddressSize;         // 地址長度Cmdhandler.DataMode = DataMode;               // 數據模式// QSPI_SIOO_INST_EVERY_CMDCmdhandler.SIOOMode = SIOOMode;Cmdhandler.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; // 無交替字節Cmdhandler.DdrMode = QSPI_DDR_MODE_DISABLE;               // 關閉DDR模式Cmdhandler.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;Cmdhandler.NbData = NbData; // 數據長度HAL_QSPI_Command(&W25Qxx_QSPI_Handle, &Cmdhandler, 5000);if (NbData != 0 && buf != NULL)HAL_QSPI_Receive(&W25Qxx_QSPI_Handle, buf, 5000);
}
#endif/*** @brief 讀取芯片的ID* @param 無* @retval 芯片的ID* @author:HZ12138* @date: 2022-07-03 20:49:18*/
uint16_t W25Qxx_Read_ID(void)
{
#ifdef W25Qxx_SSPIuint32_t zj1, zj2;W25Qxx_CS_Low();W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Device_ID);         // 發送命令W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder);       // 占位符W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder);       // 占位符W25Qxx_SPI_RW_Byte(0x00);                         // 必須為0zj1 = W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder); // 發送占位符讀取數據zj2 = W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder); // 發送占位符讀取數據W25Qxx_CS_Hight();return ((zj1 << 8) | zj2);
#endif
#ifdef W25Qxx_QSPIuint8_t temp[2];uint16_t deviceid;W25Qxx_QSPI_CMD(W25Qxx_CMD_Device_ID, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_EVERY_CMD, 0, 2, temp);deviceid = (temp[0] << 8) | temp[1];return deviceid;
#endif
}
/*** @brief 讀取數據* @param Address:要讀取的地址* @param Buf:將數據放入的數組地址* @param Len:讀取的字節數* @retval 無* @author:HZ12138* @date: 2022-07-03 20:49:18*/
void W25Qxx_Read_Data(uint32_t Address, uint8_t *Buf, uint32_t Len)
{
#ifdef W25Qxx_SSPIW25Qxx_CS_Low();W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Read_Data); // 命令if (W25Qxx_Address_Len == 32)W25Qxx_SPI_RW_Byte((Address & 0xFF000000) >> 24); // 如果是32位的地址則發送W25Qxx_SPI_RW_Byte((Address & 0x00FF0000) >> 16); // 地址W25Qxx_SPI_RW_Byte((Address & 0x0000FF00) >> 8);  // 地址W25Qxx_SPI_RW_Byte((Address & 0x000000FF) >> 0);  // 地址// for (int i = 0; i < Len; i++)//     Buf[i] = W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder);W25Qxx_SPI_Read_Len(Buf, Len);W25Qxx_CS_Hight();
#endif#ifdef W25Qxx_QSPIW25Qxx_QSPI_CMD(W25Qxx_CMD_QSPI_Read, Address, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_EVERY_CMD, 2, Len, Buf);#endif
}/*** @brief 寫保護* @param Functional:* @arg 1:     允許寫入* @arg 0:     不允許寫入* @retval 無* @author:HZ12138* @date: 2022-07-03 20:49:18*/
void W25Qxx_Write_Protect(uint8_t Functional)
{
#ifdef W25Qxx_SSPIW25Qxx_CS_Low();if (Functional == 0)W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Write_Disable); // 不允許寫入else if (Functional == 1)W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Write_Enable); // 允許寫入W25Qxx_CS_Hight();
#endif#ifdef W25Qxx_QSPIif (Functional == 0)W25Qxx_QSPI_CMD(W25Qxx_CMD_Write_Disable, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE, QSPI_SIOO_INST_EVERY_CMD, 0, 0, NULL); // 不允許寫入else if (Functional == 1)W25Qxx_QSPI_CMD(W25Qxx_CMD_Write_Enable, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE, QSPI_SIOO_INST_EVERY_CMD, 0, 0, NULL); // 允許寫入#endifW25Qxx_Wait_Free();
}/*** @brief 讀取寄存器的狀態* @retval 狀態* @author:HZ12138* @date: 2022-07-03 20:49:18*/
uint8_t W25Qxx_Read_StatusReg(uint8_t num)
{
#ifdef W25Qxx_SSPIuint8_t zj = 0;W25Qxx_CS_Low();switch (num){case 1:W25Qxx_SPI_RW_Byte(W25Qxx_CMD_ReadStatusReg1);break;case 2:W25Qxx_SPI_RW_Byte(W25Qxx_CMD_ReadStatusReg2);break;case 3:W25Qxx_SPI_RW_Byte(W25Qxx_CMD_ReadStatusReg3);break;default:break;}zj = W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder); // 接收數據W25Qxx_CS_Hight();return zj;
#endif
#ifdef W25Qxx_QSPIuint8_t temp;switch (num){case 1:W25Qxx_QSPI_CMD(W25Qxx_CMD_ReadStatusReg1, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_EVERY_CMD, 0, 1, &temp);break;case 2:W25Qxx_QSPI_CMD(W25Qxx_CMD_ReadStatusReg2, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_EVERY_CMD, 0, 1, &temp);break;case 3:W25Qxx_QSPI_CMD(W25Qxx_CMD_ReadStatusReg3, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_EVERY_CMD, 0, 1, &temp);break;default:break;}return temp;
#endif
}
/*** @brief 等待寫入/擦除完成* @param 無* @retval 無* @author:HZ12138* @date: 2022-07-03 20:49:18*/
void W25Qxx_Wait_Free(void)
{while (W25Qxx_Read_StatusReg(1) & 0x01) // 等待寫完;
}/*** @brief 扇區擦除* @param Address:要擦除的扇區內的任意地址* @retval 無* @author:HZ12138* @date: 2022-07-03 20:49:18*/
void W25Qxx_Sector_Erase(uint32_t Address)
{
#ifdef W25Qxx_SSPIAddress &= 0xFFFFF000;W25Qxx_Write_Protect(1); // 允許寫入W25Qxx_CS_Low();W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Sector_Erase); // 命令if (W25Qxx_Address_Len == 32)W25Qxx_SPI_RW_Byte((Address & 0xFF000000) >> 24); // 如果是32位的地址則發送W25Qxx_SPI_RW_Byte((Address & 0x00FF0000) >> 16); // 地址W25Qxx_SPI_RW_Byte((Address & 0x0000FF00) >> 8);  // 地址W25Qxx_SPI_RW_Byte((Address & 0x000000FF) >> 0);  // 地址W25Qxx_CS_Hight();W25Qxx_Write_Protect(0); // 關閉寫入// while (W25Qxx_Read_StatusReg1() & 0x01) //等待寫完//     ;
#endif
#ifdef W25Qxx_QSPIW25Qxx_Write_Protect(1);W25Qxx_QSPI_CMD(W25Qxx_CMD_Sector_Erase, Address, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_NONE, QSPI_SIOO_INST_EVERY_CMD, 0, 0, NULL);W25Qxx_Wait_Free();W25Qxx_Write_Protect(0);
#endif
}/*** @brief 復位* @author HZ12138* @date 2025-02-24 19:31:43*/
void W25Qxx_reset(void)
{W25Qxx_QSPI_CMD(W25Qxx_CMD_ENABLE_RESET, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE, QSPI_SIOO_INST_EVERY_CMD, 0, 0, NULL);HAL_Delay(5);W25Qxx_QSPI_CMD(W25Qxx_CMD_RESET, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE, QSPI_SIOO_INST_EVERY_CMD, 0, 0, NULL);HAL_Delay(10);
}/*** @brief 初始化* @author HZ12138* @date 2025-02-24 19:31:57*/
void W25Qxx_init(void)
{
#ifdef W25Qxx_QSPIW25Qxx_reset();W25Qxx_QSPI_CMD(W25Qxx_CMD_Enter_QSPI, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE, QSPI_SIOO_INST_EVERY_CMD, 0, 0, NULL);
#endif
}/*** @brief 按頁寫入數據* @param Address:地址 256bit/頁* @param Buf:將數據的數組地址* @param Len:寫入的字節數* @retval 無* @author:HZ12138* @date: 2022-07-03 20:49:18*/
void W25Qxx_Write_Page(uint32_t Address, uint8_t *Buf, uint32_t Len)
{
#ifdef W25Qxx_SSPIW25Qxx_Write_Protect(1); // 允許寫入W25Qxx_Wait_Free();W25Qxx_CS_Low();W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Write_Page); // 命令if (W25Qxx_Address_Len == 32)W25Qxx_SPI_RW_Byte((Address & 0xFF000000) >> 24); // 如果是32位的地址則發送W25Qxx_SPI_RW_Byte((Address & 0x00FF0000) >> 16); // 地址W25Qxx_SPI_RW_Byte((Address & 0x0000FF00) >> 8);  // 地址W25Qxx_SPI_RW_Byte((Address & 0x00000000) >> 0);  // 地址(確保是首地址)W25Qxx_SPI_Write_Len(Buf, Len);W25Qxx_CS_Hight();W25Qxx_Write_Protect(0); // 關閉寫入
#endif#ifdef W25Qxx_QSPIW25Qxx_Write_Protect(1);W25Qxx_QSPI_CMD(W25Qxx_CMD_Write_Page, Address, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_ONLY_FIRST_CMD, 0, Len, NULL);HAL_QSPI_Transmit(&W25Qxx_QSPI_Handle, Buf, 5000);W25Qxx_Wait_Free();W25Qxx_Write_Protect(0);#endif
}
/*** @brief 進入映射模式* @author HZ12138* @date 2025-02-25 23:31:05*/
void W25Qxx_Enter_MemoryMappedMode(void)
{QSPI_CommandTypeDef s_command = {0};QSPI_MemoryMappedTypeDef s_mem_mapped_cfg = {0};s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;     // 1線方式發送指令s_command.AddressSize = W25Qxx_QSPI_ADDR_SIZE;           // 32位地址s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; // 無交替字節s_command.DdrMode = QSPI_DDR_MODE_DISABLE;               // W25Q256JV不支持DDRs_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;  // DDR模式,數據輸出延遲s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;           // 每次傳輸都發指令s_command.Instruction = W25Qxx_CMD_QSPI_FAST_READ; // 快速讀取命令s_command.AddressMode = QSPI_ADDRESS_4_LINES;      // 4地址線s_command.DataMode = QSPI_DATA_4_LINES;            // 4數據線s_command.DummyCycles = 6;                         // 空周期s_mem_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;s_mem_mapped_cfg.TimeOutPeriod = 0;HAL_QSPI_MemoryMapped(&W25Qxx_QSPI_Handle, &s_command, &s_mem_mapped_cfg);
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/898706.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/898706.shtml
英文地址,請注明出處:http://en.pswp.cn/news/898706.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

(滑動窗口)算法訓練篇11--力扣3.無重復字符的最長字串(難度中等)

目錄 1.題目鏈接&#xff1a;3.無重復字符的最長字符 2.題目描述&#xff1a; 3.解法(滑動窗口)&#xff1a; 1.題目鏈接&#xff1a;3.無重復字符的最長字符 2.題目描述&#xff1a; 給定一個字符串 s &#xff0c;請你找出其中不含有重復字符的 最長 子串 的長度。 示例…

深度學習1—Python基礎

深度學習1—python基礎 你的第一個程序 print(hello world and hello deep learning!)基本數據結構 空值 (None)&#xff1a;在 Python 中&#xff0c;None 是一個特殊的對象&#xff0c;用于表示空值或缺失的值。它不同于數字 0&#xff0c;因為 0 是一個有意義的數字&#…

記一次MyBatis分頁莫名其妙的失效,首次執行合適,后續執行分頁失效且異常

代碼幾乎一樣&#xff0c;為啥這個xml配置的就會出現莫名其妙的問題呢 org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.type.TypeException: Could not set parameters for mapping: ParameterMapping{propertymybatis_plus_first, modeI…

網絡不可達

導致此問題原因較多&#xff0c;我只針對一種情況進行討論&#xff0c;如果和文中癥狀不同&#xff0c;另尋他處&#xff0c;或者死馬當活馬醫&#xff08;&#xff1f;&#xff09; 如需轉載&#xff0c;標記出處 癥狀&#xff1a; 1.ping命令網絡不可達 2.ifconfig中網卡en…

【AI News | 20250322】每日AI進展

AI Repos 1、DeTikZify 可以把草圖或圖形轉換成TikZ代碼的模型&#xff0c;可用來繪制復雜的科學圖表&#xff0c;輸入草圖或文字描述即可轉換成TikZ代碼。DeTikZify強大的地方在于它能理解圖表的語義信息&#xff0c; 能識別圖表中的不同組成部分及其含義&#xff0c;比如坐標…

Debian12生產環境配置筆記

在 Debian 12 上進行生產環境配置的詳細步驟&#xff0c;涵蓋軟件更新、基礎軟件安裝、Docker 及 Redis 部署&#xff0c;以及 Nginx 配置多個虛擬主機等內容。所有命令均以 root 用戶身份執行&#xff0c;無需添加 sudo 1. 更新軟件 首先&#xff0c;確保系統上的所有軟件包…

UE AI 模型自動生成導入場景中

打開小馬的weix 關注下 搜索“技術鏈” 回復《《動畫》》 快速推送&#xff1b; 拿到就能用輕松解決&#xff01;幫忙點個關注吧&#xff01;

【最后203篇系列】022 用Deepseek14b提取新聞事件

這算是之前一直想做的一件事&#xff0c;趁周末趕快做了。 業務意義&#xff1a;現實中有大量的輿情&#xff0c;這對我們的決策會有比較重要的作用 技術依賴&#xff1a; 1 模型基礎能力2 消息隊列3 異步獲取消息4 時間序列庫 1 模型基礎能力 大模型發展到現在&#xff0…

電池電量檢測方法介紹,開路電壓法、庫侖積分法、內阻法

開路電壓法、庫侖積分法、內阻法、卡爾曼濾波法、混合法 開路電壓法是目前最簡單的方法&#xff0c;根據電池的特性得知&#xff0c;在電池容量與開路電壓之間存在一定的函數關系&#xff0c;當得知開路電壓時&#xff0c;可以初步估算電池的剩余電量。該方法精度不高&#xf…

微調實戰 - 使用 Unsloth 微調 QwQ 32B 4bit (單卡4090)

本文參考視頻教程&#xff1a;賦范課堂 – 只需20G顯存&#xff0c;QwQ-32B高效微調實戰&#xff01;4大微調工具精講&#xff01;知識灌注問答風格微調&#xff0c;DeepSeek R1類推理模型微調Cot數據集創建實戰打造定制大模型&#xff01; https://www.bilibili.com/video/BV1…

【Elasticsearch】基于 Word2Vec 實現文章抄襲檢測

?? 博主簡介:CSDN博客專家,歷代文學網(PC端可以訪問:https://literature.sinhy.com/#/literature?__c=1000,移動端可微信小程序搜索“歷代文學”)總架構師,15年工作經驗,精通Java編程,高并發設計,Springboot和微服務,熟悉Linux,ESXI虛擬化以及云原生Docker和K8s…

多層感知機與反向傳播

1. 多層感知機&#xff08;MLP&#xff09; 多層感知機是一個由多個層組成的神經網絡&#xff0c;包括輸入層、隱藏層和輸出層。它的目標是通過學習輸入數據和輸出結果之間的關系&#xff0c;來解決各種問題&#xff08;比如分類或回歸&#xff09;。 2. 反向傳播&#xff08…

Cursor的五種高級用法

文章目錄 代碼編寫寫作編輯自動生成工作流搞定開源項目數據處理參考 代碼編寫 Cursor 最基本的功能是幫助你編寫代碼。只需使用 Composer&#xff08;CtrlI&#xff09;&#xff0c;描述你想要實現的功能&#xff0c;Cursor 就能生成相應的代碼。不滿意&#xff1f;直接告訴它…

LeetCode hot 100 每日一題(13)——73. 矩陣置零

這是一道難度為中等的題目&#xff0c;讓我們來看看題目描述&#xff1a; 給定一個 _m_ x _n_ 的矩陣&#xff0c;如果一個元素為 0 &#xff0c;則將其所在行和列的所有元素都設為 0 。請使用 原地 算法。 提示&#xff1a; m matrix.lengthn matrix[0].length1 < m, n …

Java基礎編程練習第34題-正則表達式

在Java里&#xff0c;正則表達式是一種強大的文本處理工具&#xff0c;它可以用于字符串的搜索、替換、分割和校驗等操作。正則表達式使用單個字符串來描述、匹配一系列符合某個句法規則的字符串。Java通過java.util.regex包提供了對正則表達式的支持。 以下是正則表達式在Jav…

基于基于eFish-SBC-RK3576工控板的智慧城市邊緣網關

此方案充分挖掘eFish-SBC-RK3576的硬件潛力&#xff0c;可快速復制到智慧園區、交通樞紐等場景。 方案亮點 ?接口高密度?&#xff1a;單板集成5GWiFi多路工業接口&#xff0c;減少擴展復雜度。?AIoT融合?&#xff1a;邊緣端完成傳感器數據聚合與AI推理&#xff0c;降低云端…

redis解決緩存穿透/擊穿/雪崩

文章目錄 1.緩存穿透1.1 概念1.2 解決方案1.2.1 緩存空對象1.2.2 布隆過濾 1.2 店鋪查詢使用緩存穿透解決方案1.2.1 流程 2.緩存雪崩2.1 什么是緩存雪崩&#xff1f;2.2 雪崩解決方案 3.緩存擊穿3.1 什么是緩存擊穿&#xff1f;3.2解決方案3.2.1 基于互斥鎖解決緩存擊穿問題&am…

第十六屆藍橋杯康復訓練--6

題目鏈接&#xff1a;790. 數的三次方根 - AcWing題庫 思路&#xff1a;二分&#xff0c;注意正負號和小數判斷退出的方法&#xff08;雖然正負無所謂&#xff09; 代碼&#xff1a; #include<bits/stdc.h> using namespace std;#define exs 0.00000018812716007232667…

Vue學習筆記集--路由

路由 Vue Router 是 Vue.js 官方的路由管理器&#xff0c;用于在 Vue 應用程序中實現單頁面應用&#xff08;SPA&#xff09;的路由功能。以下是 Vue Router 的基本使用方法&#xff1a; 安裝 Vue Router 如果你使用的是 Vue 2&#xff0c;可以通過 npm 安裝 Vue Router&…

【AVRCP】深度剖析 AVRCP 中 Generic Access Profile 的要求與應用

目錄 一、GAP基礎架構與核心要求 1.1 GAP在藍牙體系中的定位 1.2 核心模式定義 二、AVRCP對GAP的增強要求 2.1 模式擴展規范 2.2 空閑模式過程支持 三、安全機制實現細節 3.1 認證與加密流程 3.2 安全模式要求 四、設備發現與連接建立 4.1 發現過程狀態機 4.2 連接…