深入淺出SPI通信協議與STM32實戰應用(W25Q128驅動)(實戰部分)

1. W25Q128簡介

W25Q128 是Winbond推出的128M-bit(16MB)SPI接口Flash存儲器,支持標準SPI、Dual-SPI和Quad-SPI模式。關鍵特性:

  • 工作電壓:2.7V~3.6V
  • 分頁結構:256頁/塊,每塊16KB,共1024塊
  • 支持頁編程(256字節/頁)
  • 擦除操作支持:扇區擦除(4KB)、塊擦除(32/64KB)、全片擦除
  • 最高104MHz時鐘頻率
  • 超過10萬次擦寫周期
    在這里插入圖片描述

1.1 W25Q128存儲架構

W25Q128存儲器采用分級存儲架構,總容量為16MB(128Mbit)。其物理存儲結構按以下層級劃分:

  • 頂層劃分:256個存儲塊(Block),每塊容量64KB
  • 塊內結構:每個存儲塊包含16個扇區(Sector),每扇區容量4KB
  • 扇區組成:每個扇區細分為16個存儲頁(Page),每頁容量256字節

該存儲器的擦除機制具有以下特性:

  • 最小擦除單位為單個扇區(4KB)
  • 執行擦除操作時必須完整處理整個扇區
  • 系統需預置4KB對齊的緩沖區,以滿足物理擦除粒度要求在這里插入圖片描述

1.2 W25Q128常用指令

W25Q128 有非常多的指令,我們介紹幾個常用的指令。

指令(HEX)名稱 作用
0x06寫使能寫入數據/擦除之前,必須先發送該指令
0x05讀 SR1判定 FLASH 是否處于空閑狀態,擦除用
0x03讀數據讀取數據
0x02頁寫寫入數據,最多寫256字節
0x20扇區擦除扇區擦除指令,最小擦除單位

1.3 W25Q128 操作時序詳解

  1. 寫使能(06h)- 指令功能?:執行編程/擦除操作的必要使能信號
    • 操作流程:
      ① CS引腳置低(通信起始)
      ② 發送1字節指令碼06h(MSB先發)
      ③ CS引腳置高(通信終止)
    • 狀態影響?:WEL位(SR1)置1,有效時間約50ms

  1. 狀態寄存器讀取(05h)
    • 指令格式?:
      ┌──────┬─────────────┐
      | 指令碼 | 響應數據(持續可讀) |
      └──────┴─────────────┘
    • 操作時序**?:
      ① CS置低 → 發送05h → 連續讀取SR1 → CS置高?
    • 技術要點?:
      BUSY位(SR1)監控:0=空閑,1=操作中
      支持全雙工SPI模式(MOSI/MISO同步傳輸)

  1. 數據讀取(03h)
    • 指令結構?:
      ┌──────┬──────────┬─────────┐
      | 03h | 24bit地址 | 數據流(≥1字節) |
      └──────┴──────────┴─────────┘
    • 執行步驟?:
      1. 起始地址對齊(按頁邊界自動遞增)
      2. 單次讀取最大長度:256字節(單頁容量)
      3. 跨頁讀取需重新發送地址指令

  1. 頁編程(02h)
    • 技術限制?:
      • 目標區域必須處于擦除狀態(全FFh)
      • 單次寫入跨度≤256字節(禁止跨頁寫入)
    • 操作序列?:
      [CS↓] → 02h → A23-A0 → Data_1~Data_n → [CS↑]
    • 物理特性?:
      實際編程時間約0.7-1.2ms(需BUSY狀態輪詢)

  1. 扇區擦除(20h)
    • 約束條件?:
      • 擦除粒度:4KB(地址自動對齊到扇區起始)
      • 擦除后狀態:全FFh(電平=1)
    • 時序流程?:
      [CS↓] → 20h → 目標地址(A23-A0) → [CS↑]
    • 耗時范圍?:
      典型值400ms(溫度25℃),最大600ms

2. 硬件連接

這篇文章,使用的是W25Q128模塊,W25Q128 的模塊各個廠家做的各有不同,只是長得不一樣而已,使用方式、引腳都是一樣的。
通過產品手冊如下:在這里插入圖片描述

STM32F103C8T6W25Q128 接線方式:

STM32引腳W25Q128引腳功能
PA5CLKSPI時鐘
PA6MISO主機輸入
PA7MOSI主機輸出
PA4CS片選信號
3.3VVCC電源
GNDGND

注意:CS引腳需通過GPIO控制,不可固定接低電平

3. 代碼實現

在這里插入圖片描述

3.1 SPI及GPIO初始化

在進行初始化時, 我們需要查看參考手冊:在這里插入圖片描述
按照手冊和實際情況進行GPIO口的配置

SPI_HandleTypeDef spi_handle = {0};
void w25q128_spi_init(void)
{spi_handle.Instance = SPI1;spi_handle.Init.Mode = SPI_MODE_MASTER;spi_handle.Init.Direction = SPI_DIRECTION_2LINES;spi_handle.Init.DataSize = SPI_DATASIZE_8BIT;spi_handle.Init.CLKPolarity = SPI_POLARITY_LOW;              /* CPOL = 0 */spi_handle.Init.CLKPhase = SPI_PHASE_1EDGE;                  /* CPHA = 0 */spi_handle.Init.NSS = SPI_NSS_SOFT;spi_handle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;spi_handle.Init.FirstBit = SPI_FIRSTBIT_MSB;spi_handle.Init.TIMode = SPI_TIMODE_DISABLE;spi_handle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;spi_handle.Init.CRCPolynomial = 7;HAL_SPI_Init(&spi_handle);
}void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{if(hspi->Instance == SPI1){GPIO_InitTypeDef gpio_initstruct;//打開時鐘__HAL_RCC_GPIOA_CLK_ENABLE();                           // 使能GPIOB時鐘__HAL_RCC_SPI1_CLK_ENABLE();//調用GPIO初始化函數gpio_initstruct.Pin = GPIO_PIN_4;          gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP;           gpio_initstruct.Pull = GPIO_PULLUP;                    gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;          HAL_GPIO_Init(GPIOA, &gpio_initstruct);gpio_initstruct.Pin = GPIO_PIN_5 | GPIO_PIN_7;          gpio_initstruct.Mode = GPIO_MODE_AF_PP;           HAL_GPIO_Init(GPIOA, &gpio_initstruct);gpio_initstruct.Pin = GPIO_PIN_6;          gpio_initstruct.Mode = GPIO_MODE_INPUT;           HAL_GPIO_Init(GPIOA, &gpio_initstruct);}
}

3.2 W25Q128驅動函數

w251128.c

// 交換一個字節
uint8_t w25q128_spi_swap_byte(uint8_t data)
{uint8_t recv_data = 0;HAL_SPI_TransmitReceive(&spi_handle, &data, &recv_data, 1, 1000);return recv_data;
}// 初始化:
void w25q128_init(void)
{w25q128_spi_init();
}// 讀取iD值
uint16_t w25q128_read_id(void)
{uint16_t device_id = 0;W25Q128_CS(0);w25q128_spi_swap_byte(FLASH_ManufactDeviceID);w25q128_spi_swap_byte(0x00);w25q128_spi_swap_byte(0x00);w25q128_spi_swap_byte(0x00);device_id = w25q128_spi_swap_byte(FLASH_DummyByte) << 8;device_id |= w25q128_spi_swap_byte(FLASH_DummyByte);W25Q128_CS(1);return device_id;
}// 寫使能void w25q128_writ_enable(void)
{W25Q128_CS(0);w25q128_spi_swap_byte(FLASH_WriteEnable);W25Q128_CS(1);
}// 讀取狀態寄存器1
uint8_t w25q128_read_sr1(void)
{uint8_t recv_data = 0;W25Q128_CS(0);w25q128_spi_swap_byte(FLASH_ReadStatusReg1);recv_data = w25q128_spi_swap_byte(FLASH_DummyByte);W25Q128_CS(1);return recv_data;
}// 等待BUSY
void w25q128_wait_busy(void)
{while((w25q128_read_sr1() & 0x01) == 0x01);
}
// 發送地址
void w25q128_send_address(uint32_t address)
{w25q128_spi_swap_byte(address >> 16);w25q128_spi_swap_byte(address >> 8);w25q128_spi_swap_byte(address);
}
// 讀取數據
void w25q128_read_data(uint32_t address, uint8_t *data, uint32_t size)
{uint32_t i = 0;W25Q128_CS(0);w25q128_spi_swap_byte(FLASH_ReadData);w25q128_send_address(address);for(i = 0; i < size; i++)data[i] = w25q128_spi_swap_byte(FLASH_DummyByte);W25Q128_CS(1);
}// 頁寫
void w25q128_write_page(uint32_t address, uint8_t *data, uint16_t size)
{uint16_t i = 0;w25q128_writ_enable();W25Q128_CS(0);w25q128_spi_swap_byte(FLASH_PageProgram);w25q128_send_address(address);for(i = 0; i < size; i++)w25q128_spi_swap_byte(data[i]);W25Q128_CS(1);//等待空閑w25q128_wait_busy();
}// 擦除
void w25q128_erase_sector(uint32_t address)
{//寫使能w25q128_writ_enable();//等待空閑w25q128_wait_busy();//拉低片選W25Q128_CS(0);//發送扇區擦除指令w25q128_spi_swap_byte(FLASH_SectorErase);//發送地址w25q128_send_address(address);//拉高片選W25Q128_CS(1);//等待空閑w25q128_wait_busy();
}
w25q128.h
#ifndef __W25Q128_H__
#define __W25Q128_H__#include "sys.h"#define W25Q128_CS(x)   do{ x ? \HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET): \HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); \}while(0)/* 指令表 */
#define FLASH_ManufactDeviceID                  0x90
#define FLASH_WriteEnable                       0x06
#define FLASH_ReadStatusReg1                    0x05
#define FLASH_ReadData                          0x03
#define FLASH_PageProgram                       0x02
#define FLASH_SectorErase                       0x20
#define FLASH_DummyByte                         0xFFvoid w25q128_init(void);
uint16_t w25q128_read_id(void);
void w25q128_read_data(uint32_t address, uint8_t *data, uint32_t size);
void w25q128_write_page(uint32_t address, uint8_t *data, uint16_t size);
void w25q128_erase_sector(uint32_t address);#endif

3.3 使用示例

#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "w25q128.h"uint8_t data_write[4] = {0xAA, 0xBB, 0xCC, 0xDD};
uint8_t data_read[4] = {0};
int main(void)
{HAL_Init();                         /* 初始化HAL庫 */stm32_clock_init(RCC_PLL_MUL9);     /* 設置時鐘, 72Mhz */led_init();                         /* 初始化LED燈 */uart1_init(115200);w25q128_init();printf("hello world!\r\n");uint16_t device_id = w25q128_read_id();printf("device id: %X\r\n", device_id);//    w25q128_erase_sector(0x000000);
//    w25q128_write_page(0x000000, data_write, 4);w25q128_read_data(0x000000, data_read, 4);printf("data read: %X, %X, %X, %X\r\n", data_read[0], data_read[1], data_read[2], data_read[3]);while(1){ }
}

4. 注意事項

  1. 擦除操作必需:Flash寫入前必須擦除對應區域
  2. 頁邊界限制:單次寫入不可跨頁(每頁256字節)
  3. 時鐘配置:確保SPI時鐘不超過芯片規格(建議初始使用較低頻率)
  4. 狀態檢測:重要操作后需檢查BUSY位
  5. 電源穩定:Flash操作期間需保持3.3V穩定

5. 常見問題排查

  • 無法讀取ID:檢查SPI模式(CPOL=0/CPHA=0),確認CS信號時序
  • 寫入失敗:確保已執行寫使能命令,檢查電源電壓
  • 數據錯誤:注意地址對齊,排除SPI時鐘干擾

完整工程代碼可在Gitee獲取:https://gitee.com/bad-lemon/mcu-development-record.git


實現效果:通過上述代碼可實現W25Q128的讀寫操作,實測寫入速度可達600KB/s,讀取速度1.2MB/s(SPI時鐘18MHz)。該方案適用于需要大容量非易失存儲的物聯網設備、數據采集系統等場景。

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

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

相關文章

STM32 HAL庫之EXTI示例代碼

外部中斷按鍵控制LED燈 在main.c中 HAL_Init(); 初始化Flash&#xff0c;中斷優先級以及HAL_MspInit函數&#xff0c;也就是 stm32f1xx_hal.c 中 HAL_StatusTypeDef HAL_Init(void) {/* Configure Flash prefetch */ #if (PREFETCH_ENABLE ! 0) #if defined(STM32F101x6) || …

查看手機在線狀態,保障設備安全運行

手機作為人們日常生活中不可或缺的工具&#xff0c;承載著溝通、工作、娛樂等多種功能。保障手機設備的安全運行是我們每個人都非常重要的任務&#xff0c;而了解手機的在線狀態則是其中的一環。通過挖數據平臺提供的在線查詢工具&#xff0c;我們可以方便快捷地查詢手機號的在…

Llama 4全面評測:官方數據亮眼,社區測試顯不足之處

引言 2025年4月&#xff0c;Meta正式發布了全新的Llama 4系列模型&#xff0c;這標志著Llama生態系統進入了一個全新的時代。Llama 4不僅是Meta首個原生多模態模型&#xff0c;還采用了混合專家(MoE)架構&#xff0c;并提供了前所未有的上下文長度支持。本文將詳細介紹Llama 4…

淘寶API驅動跨境選品:多語言詳情頁自動翻譯與本地化定價

淘寶 API 驅動跨境選品實現多語言詳情頁自動翻譯與本地化定價&#xff0c;為跨境電商業務帶來諸多便利與優勢&#xff0c;以下是詳細介紹&#xff1a; 一、多語言詳情頁自動翻譯 技術原理 借助淘寶的 API 接口&#xff0c;獲取商品詳情頁的各類文本信息&#xff0c;包括標題、描…

MFC工具欄CToolBar從專家到小白

CToolBar m_wndTool; //創建控件 m_wndTool.CreateEx(this, TBSTYLE_FLAT|TBSTYLE_NOPREFIX, WS_CHILD | WS_VISIBLE | CBRS_FLYBY | CBRS_TOP | CBRS_SIZE_DYNAMIC); //加載工具欄資源 m_wndTool.LoadToolBar(IDR_TOOL_LOAD) //在.rc中定義&#xff1a;IDR_TOOL_LOAD BITMAP …

【Java面試系列】Spring Boot微服務架構下的分布式事務處理與性能優化詳解 - 3-5年Java開發必備知識

【Java面試系列】Spring Boot微服務架構下的分布式事務處理與性能優化詳解 - 3-5年Java開發必備知識 引言 在當今的微服務架構中&#xff0c;分布式事務處理和性能優化是面試中經常被問及的高頻話題。隨著系統規模的擴大&#xff0c;如何保證數據一致性和系統性能成為了開發者…

【動態規劃】 深入動態規劃—兩個數組的dp問題

文章目錄 前言例題一、最長公共子序列二、不相交的線三、不同的子序列四、通配符匹配五、交錯字符串六、兩個字符串的最小ASCII刪除和七、最長重復子數組 結語 前言 問題本質 它主要圍繞著給定的兩個數組展開&#xff0c;旨在通過對這兩個數組元素間關系的分析&#xff0c;找出…

【C++面向對象】封裝(上):探尋構造函數的幽微之境

每文一詩 &#x1f4aa;&#x1f3fc; 我本將心向明月&#xff0c;奈何明月照溝渠 —— 元/高明《琵琶記》 譯文&#xff1a;我本是以真誠的心來對待你&#xff0c;就像明月一樣純潔無瑕&#xff1b;然而&#xff0c;你卻像溝渠里的污水一樣&#xff0c;對這份心意無動于衷&a…

JavaScript性能優化(下)

1. 使用適當的算法和邏輯 JavaScript性能優化是一個復雜而重要的話題&#xff0c;尤其是在構建大型應用時。通過使用適當的算法和邏輯&#xff0c;可以顯著提高代碼的效率和響應速度。以下是一些關鍵策略和實踐&#xff0c;用于優化JavaScript性能&#xff1a; 1.1. 采用適當…

螞蟻 Flink 實時計算編譯任務 Koupleless 架構改造

張馮君&#xff08;遠遠&#xff09; Koupleless PMC 螞蟻集團技術工程師 就職于螞蟻集團中間件團隊&#xff0c;參與維護與建設螞蟻 SOFAArk 和 Koupleless 開源項目、內部 SOFAServerless 產品的研發和實踐。 本文 3488 字&#xff0c;預計閱讀 11 分鐘 業務背景 基于開源 A…

使用pycharm社區版調試DIFY后端python代碼

目錄 背景 前置條件 DIFY使用的框架 API服務調試配置步驟&#xff08;基于tag為0.15.3的版本&#xff09; 1.配置.env文件 2.關閉docker里面的docker-api-1服務 3.使用DOCKER啟動本地環境需要用到的中間件&#xff0c;并暴露端口 注意事項一&#xff1a; 注意事項二&#xff1a…

從 macos 切換到 windows 上安裝的工具類軟件

起因 用了很多年的macos, 已經習慣了macos上的操作, 期望能在windows上獲得類似的體驗, 于是花了一些時間來找windows上相對應的軟件. 截圖軟件 snipaste?????? windows和macos都有的軟件, 截圖非常好用 文件同步軟件 oneDrive: 嘗試了不同的同步軟件, 還是微軟在各…

MySQL體系架構(一)

1.1.MySQL的分支與變種 MySQL變種有好幾個,主要有三個久經考驗的主流變種:Percona Server,MariaDB和 Drizzle。它們都有活躍的用戶社區和一些商業支持,均由獨立的服務供應商支持。同時還有幾個優秀的開源關系數據庫,值得我們了解一下。 1.1.1.Drizzle Drizzle是真正的M…

【項目實訓項目博客】prompt初版實踐

通過對camel技術的理解&#xff0c;我們向其中添加了市場營銷角色的prompt 初版設計如下&#xff1a; chatchainconfig.json { "chain": [ { "phase": "DemandAnalysis", "phaseType": "SimplePhase", "max_turn_step…

[Bond的雜貨鋪] CKS 證書也到貨咯

最近比較忙&#xff0c;忘記寫Blog了&#xff1a;&#xff09; 一年前黑五去官網蹲了一手Cyber Monday&#xff0c;買了英文考試券bundle&#xff0c;當時只考了cka,后來cks差點都忘記了。將近一年后&#xff0c;無意中收到官方的提醒郵件&#xff0c;說考試券本已過期&#x…

【回眸】Linux 內核 (十五) 之 多線程編程 上

前言 進程和線程 區別 線程API 1.創建線程 2.線程退出 3.線程等待 4.線程脫離 5. 線程ID獲取及比較 6.創建及銷毀互斥鎖 7.創建及銷毀條件變量 8. 等待 9.觸發 多線程編程 后記 前言 高產的幾天。 進程和線程 區別 進程——資源分配的最小單位&#xff0c;線…

127.0.0.1本地環回地址(Loopback Address)

127.0.0.1 是計算機網絡中的一個特殊IPv4地址&#xff0c;稱為本地環回地址&#xff08;Loopback Address&#xff09;&#xff0c;主要用于以下用途&#xff1a; 1. 基本定義 本地主機&#xff08;Localhost&#xff09;&#xff1a;該地址始終指向當前正在使用的計算機本身&a…

S7-1200 PLC熱電偶和熱電阻模擬量模塊

熱電偶和熱電阻模擬量模塊 S7-1200 PLC有專用用于對溫度進行采集的熱電偶模塊SM1231 TC和SM 1231RTD。熱電偶模塊有4AI和8AI兩種&#xff0c;下面以SM1231 TC 4AI為例看一下接線圖。 該模塊一共有4個通道&#xff0c;每個通道有兩個接線端子&#xff0c;比如0&#xff0c;0-。…

深度了解向量引論

今天去研究了一個基本數學原理 這個其實需要證明 今天推導了一下這個公式&#xff0c;感覺收獲挺大 下面是手工推導過程

Feign修仙指南:聲明式HTTP請求的優雅之道

各位在微服務世界摸爬滾打的道友們&#xff01;今天要解鎖的是Spring Cloud的絕世神通——Feign&#xff01;這貨堪稱HTTP界的"言出法隨"&#xff0c;只需定義接口&#xff0c;就能自動生成HTTP請求代碼&#xff01;從此告別手動拼裝URL的苦日子&#xff0c;讓你的代…