概述
FLASH
FLASH是一種是非易失性存儲器,即掉電后不會丟失數據,這和RAM(隨機存儲器)不同。
FLASH比起同作用的EEPROM有價格低的優點
FLASH的擦除操作是以扇區為單位的(比起EEPROM來說操作較為不方便)
芯片型號
本文介紹的W25Qxx系列芯片有以下型號
型號 | 容量 | ID | 最大地址(最小為0x0) |
---|---|---|---|
W25Q16 | 2MByte(16Mbit) | 0xEF14 | 0x1FFFFF |
W25Q32 | 4MByte(32Mbit) | 0xEF15 | 0x3FFFFF |
W25Q64 | 8MByte(64Mbit) | 0xEF16 | 0x7FFFFF |
W25Q128 | 16MByte(128Mbit) | 0xEF17 | 0xFFFFFF |
W25Q256 | 32MByte(256Mbit) | 0xEF18 | 0x01FFFFFF |
W25Q512 | 64MByte(512Mbit) | 0xEF19 | 0x03FFFFFF |
W25Q16、W25Q32、W25Q64、W25Q128的地址為3字節(3x8=24bit)
W25Q256和W25Q512的地址為4字節(4x8=32bit)
本文以W25Q64為例,代碼已經做了兼容處理,可以兼容所有種類芯片
電氣連接
常規SPI協議的W25Qxx
芯片支持的工作電壓 2.7V 到 3.6V,正常工作時電流小于5mA,掉電時低于1uA
注意
1.W25Qxx的封裝和通信協議有許多種,本文以最常見的SOT-8封裝和SPI通信為例
2.FLASH因為物理限制(相比EEPROM省成本),只能將1變成0,所以需要在寫入數據前進行擦除操作
W25Qxx的數據組成
下圖是數據手冊的截圖(W25Q64)
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)
地址的構成如下圖
標準SPI
SPI配置
因為是存儲芯片的驅動,往往需要較大的傳輸速度以支持快速讀取數據
所以使用硬件SPI進行通信
配置內容
1.全雙工主機模式
2.軟件觸發NSS(片選)
3.數據長度8bit
4.先發送MSB(高位)
5.分頻后的時鐘要小于80MHz
6.時鐘極性(CPOL)為低電平有效,時鐘相位(CPHA)為第一個邊沿
7.不啟用CRC校驗
8.不開啟DMA和中斷(DMA傳輸的以后再說)
片選信號
使用軟件觸發需要設置另外的GPIO
必須設置為推挽浮空輸出,最高等級(其他設置會出現問題)
HAL配置
硬件SPI
GPIO
基礎命令
命令介紹
這部分介紹一下W25Qxx的命令構成,這是數據手冊的截圖
使用的是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
數據手冊的截圖
流程
- 片選選中(低電平)
- 發送讀取ID命令(0x90)
- 發送2個字節(Byte)的占位符(任意數據即可)
- 發送0x00
- 接收2個字節的數據(也需要發送2個字節的占位符)
- 片選釋放(高電平)
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);
}
寫使/失能
數據手冊的截圖
寫使能是0x06,寫失能是0x04
調用之后即可一直保持失能/使能狀態
默認狀態是不允許寫入數據(失能),這是為了保護數據,避免誤操作
因此每次上電后想要寫入數據需要使能寫入
建議每次寫入完成后 將寫入失能
為了便于使用,將寫入使能和失能放入同一個函數中
流程
- 片選選中(低電平)
- 根據輸入選擇命令發送
- 片選釋放(高電平)
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的截圖,用到時再細說各個位的功能
讀狀態寄存器1的命令 和時序
流程
- 片選選中(低電平)
- 發送命令并接收數據
- 片選釋放(高電平)
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的擦除最小單位為扇區
流程
- 片選選中(低電平)
- 發送命令
- 發送地址(只要是扇區內的任意地址即可)
- 片選釋放(高電平)
注意:
- 地址長度需要根據不同芯片來定(24bit或32bit)
- 地址是扇區內的任意一個地址即可
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) //等待寫完// ;
}
讀取數據
讀取數據的命令
流程
- 片選選中(低電平)
- 發送命令
- 發送起始地址
- 接收數據
- 片選釋放(高電平)
注意
- 地址為起始地址
- 地址會自增
- 可以跨頁,區塊,扇區
設計為輸入起始地址,將數據放到的數字的地址,長度
將讀取的數據放入指定的位置
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();
}
按頁寫入數據
本函數是為了之后的數據寫入做的準備函數
流程
- 片選選中(低電平)
- 發送命令
- 發送地址
- 發送數據
- 片選釋放(高電平)
注意:
- 本命令不可以跨頁使用,如果發送的數據長度小于本頁剩余長度,則會從本頁最開頭覆蓋數據
- 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");}
}
扇區單位寫入數據
上文說過,芯片擦除數據的最小單位為扇區
因此進行數據寫入時,會將整個扇區的數據都給擦除,我們需要建立一個緩沖區來存儲數據,才能精確更改幾個數據
流程:
- 讀取這個扇區原來的數據到緩沖區
- 根據需要寫入數據到緩沖區
- 擦除這個扇區的數據
- 將緩沖區的數據寫回芯片
用到了一些位操作,建議配合前文的地址構成理解
讀取和寫入的地址計算和上文的讀取扇區數據一樣, (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部分,第一個扇區,中間扇區,最后一個扇區
- 第一個扇區的數據長度不定,數據長度為(本扇區結束地址-起始地址)
- 中間扇區數據長度確定,數據長度為4096
- 最后一個扇區數據長度不定,數據長度為(結束地址-本扇區起始地址)
- 只需要根據計數以此推進輸入數據再調用寫入扇區的函數即可
這個是第一個扇區的數據寫入
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配置
開啟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即可
其余默認即可
注意GPIO中要選擇最高模式
命令
HAL庫API
QSPI命令結構體
類型 | 名稱 | 功能 |
---|---|---|
uint32_t | Instruction | 命令(8bits) |
uint32_t | Address | 地址(32bits) |
uint32_t | AlternateBytes | 備用字節(32bits) |
uint32_t | AddressSize | QSPI_ADDRESS_8_BITS 到QSPI_ADDRESS_32_BITS |
uint32_t | AlternateBytesSize | 交換字節大小(未用到) |
uint32_t | DummyCycles | 空周期(0-31) |
uint32_t | InstructionMode | 指令模式(QSPI_INSTRUCTION_NONE 等) |
uint32_t | AddressMode | 地址模式(QSPI_ADDRESS_NONE 等) |
uint32_t | AlternateByteMode | 字節交換模式(QSPI_ALTERNATE_BYTES_NONE 等 |
uint32_t | DataMode | 數據模式(QSPI_DATA_NONE 等) |
uint32_t | NbData | 數據長度(32bits)(0表示不確定的長度直到內存結束) |
uint32_t | DdrMode | DDR模式(一般關閉即可QSPI_DDR_MODE_DISABLE ) |
uint32_t | DdrHoldHalfCycle | ddr保持周期(一般QSPI_DDR_HHC_ANALOG_DELAY ) |
uint32_t | SIOOMode | 是否單獨命令(一般每次都發命令QSPI_SIOO_INST_EVERY_CMD ) |
QSPI內存映射結構體
類型 | 名稱 | 功能 |
---|---|---|
uint32_t | TimeOutPeriod | FIIFO滿等待數(16bits) |
uint32_t | TimeOutActivation | 是否使用超時(QSPI_TIMEOUT_COUNTER_DISABLE ) |
命令發送
類型 | 名稱 | 功能 |
---|---|---|
QSPI_HandleTypeDef | hqspi | 句柄 |
QSPI_CommandTypeDef | cmd | 控制結構體 |
uint32_t | Timeout | 超時 |
HAL_StatusTypeDef | 返回 | 狀態 |
HAL_StatusTypeDef HAL_QSPI_Command(QSPI_HandleTypeDef *hqspi, QSPI_CommandTypeDef *cmd, uint32_t Timeout)
獲取數據
類型 | 名稱 | 功能 |
---|---|---|
QSPI_HandleTypeDef | hqspi | 句柄 |
uint8_t | pData | 數據緩沖區 |
uint32_t | Timeout | 超時 |
HAL_StatusTypeDef | 返回 | 狀態 |
HAL_StatusTypeDef HAL_QSPI_Receive(QSPI_HandleTypeDef *hqspi, uint8_t *pData, uint32_t Timeout)
進入內存映射
類型 | 名稱 | 功能 |
---|---|---|
QSPI_HandleTypeDef | hqspi | 句柄 |
QSPI_CommandTypeDef | cmd | 命令結構體 |
QSPI_MemoryMappedTypeDef | cfg | 內存映射結構體 |
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);
}