STM32外設SPI FLASH應用實例

STM32外設SPI FLASH應用實例

  • 1. 前言
    • 1.1 硬件準備
    • 1.2 軟件準備
  • 2. 硬件連接
  • 3. 軟件實現
    • 3.1 SPI 初始化
    • 3.2 QW128 SPI FLASH 驅動
    • 3.3 乒乓存儲實現
  • 4. 測試與驗證
    • 4.1 數據備份測試
    • 4.2 數據恢復測試
  • 5 實例
    • 5.1 參數結構體定義
    • 5.2 存儲參數到 SPI FLASH
    • 5.3 從 SPI FLASH 讀取參數
    • 5.4 示例:存儲和讀取參數
    • 5.6 注意事項
  • 6. 總結

1. 前言

在嵌入式系統中,數據的存儲和備份是一個非常重要的功能。SPI FLASH 是一種常見的非易失性存儲器,具有容量大、速度快、接口簡單等優點。本文將介紹如何在 STM32F103 上使用 SPI 接口操作 QW128 SPI FLASH,并通過乒乓存儲的方式實現數據備份。

1.1 硬件準備

  • STM32F103 開發板
  • QW128 SPI FLASH 模塊
  • 杜邦線若干

1.2 軟件準備

  • Keil MDK 或 STM32CubeIDE
  • STM32 HAL 庫

2. 硬件連接

將 QW128 SPI FLASH 模塊與 STM32F103 開發板連接,具體連接方式如下:

QW128 引腳STM32F103 引腳
CSPA4
SCKPA5
MISOPA6
MOSIPA7
GNDGND
VCC3.3V

在這里插入圖片描述

3. 軟件實現

使用STM32CUBE配置SPI通信
在這里插入圖片描述
在這里插入圖片描述

在這里插入圖片描述

3.1 SPI 初始化

首先,我們需要初始化 SPI 接口。使用 STM32CubeMX 配置 SPI1 外設,并生成初始化代碼。

void MX_SPI1_Init(void)
{hspi1.Instance = SPI1;hspi1.Init.Mode = SPI_MODE_MASTER;hspi1.Init.Direction = SPI_DIRECTION_2LINES;hspi1.Init.DataSize = SPI_DATASIZE_8BIT;hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;hspi1.Init.NSS = SPI_NSS_SOFT;hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;hspi1.Init.TIMode = SPI_TIMODE_DISABLE;hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;hspi1.Init.CRCPolynomial = 10;if (HAL_SPI_Init(&hspi1) != HAL_OK){Error_Handler();}
}

3.2 QW128 SPI FLASH 驅動

接下來,我們編寫 QW128 SPI FLASH 的驅動代碼,包括讀寫操作。

#define QW128_CMD_WRITE_ENABLE 0x06
#define QW128_CMD_WRITE_DISABLE 0x04
#define QW128_CMD_READ_STATUS_REG 0x05
#define QW128_CMD_WRITE_STATUS_REG 0x01
#define QW128_CMD_READ_DATA 0x03
#define QW128_CMD_PAGE_PROGRAM 0x02
#define QW128_CMD_SECTOR_ERASE 0x20
#define QW128_CMD_CHIP_ERASE 0xC7void QW128_WriteEnable(void)
{uint8_t cmd = QW128_CMD_WRITE_ENABLE;HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}void QW128_WriteDisable(void)
{uint8_t cmd = QW128_CMD_WRITE_DISABLE;HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}uint8_t QW128_ReadStatusReg(void)
{uint8_t cmd = QW128_CMD_READ_STATUS_REG;uint8_t status;HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);HAL_SPI_Receive(&hspi1, &status, 1, HAL_MAX_DELAY);HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);return status;
}void QW128_WriteStatusReg(uint8_t status)
{uint8_t cmd[2] = {QW128_CMD_WRITE_STATUS_REG, status};QW128_WriteEnable();HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);HAL_SPI_Transmit(&hspi1, cmd, 2, HAL_MAX_DELAY);HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}void QW128_ReadData(uint32_t addr, uint8_t *data, uint16_t len)
{uint8_t cmd[4] = {QW128_CMD_READ_DATA, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF};HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY);HAL_SPI_Receive(&hspi1, data, len, HAL_MAX_DELAY);HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}void QW128_PageProgram(uint32_t addr, uint8_t *data, uint16_t len)
{uint8_t cmd[4] = {QW128_CMD_PAGE_PROGRAM, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF};QW128_WriteEnable();HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY);HAL_SPI_Transmit(&hspi1, data, len, HAL_MAX_DELAY);HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}void QW128_SectorErase(uint32_t addr)
{uint8_t cmd[4] = {QW128_CMD_SECTOR_ERASE, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF};QW128_WriteEnable();HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY);HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}void QW128_ChipErase(void)
{uint8_t cmd = QW128_CMD_CHIP_ERASE;QW128_WriteEnable();HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}

3.3 乒乓存儲實現

乒乓存儲是一種常用的數據備份策略,通過交替使用兩個存儲區域來確保數據的完整性和可靠性。

#define PAGE_SIZE 256
#define SECTOR_SIZE 4096
#define BUFFER_SIZE 1024uint8_t buffer[BUFFER_SIZE];
uint32_t current_sector = 0;void PingPong_Backup(uint8_t *data, uint16_t len)
{// 擦除當前扇區QW128_SectorErase(current_sector * SECTOR_SIZE);// 寫入數據for (uint16_t i = 0; i < len; i += PAGE_SIZE){QW128_PageProgram(current_sector * SECTOR_SIZE + i, data + i, PAGE_SIZE);}// 切換到下一個扇區current_sector = (current_sector + 1) % 2;
}void PingPong_Restore(uint8_t *data, uint16_t len)
{// 讀取數據QW128_ReadData(current_sector * SECTOR_SIZE, data, len);
}

4. 測試與驗證

4.1 數據備份測試

uint8_t test_data[BUFFER_SIZE];
for (uint16_t i = 0; i < BUFFER_SIZE; i++)
{test_data[i] = i % 256;
}PingPong_Backup(test_data, BUFFER_SIZE);

4.2 數據恢復測試

uint8_t restore_data[BUFFER_SIZE];
PingPong_Restore(restore_data, BUFFER_SIZE);// 驗證數據
for (uint16_t i = 0; i < BUFFER_SIZE; i++)
{if (restore_data[i] != test_data[i]){// 數據不一致,處理錯誤Error_Handler();}
}

5 實例

5.1 參數結構體定義

以下是參數結構體的定義,基于你提供的代碼:

typedef enum
{BAUD_9600,BAUD_19200,BAUD_115200
} BAUD_ENUM;typedef struct
{BAUD_ENUM CommBaud;          // 通信波特率uint8_t OnOffCtrl;           // 啟停操作方式(0-本地;1-遠程485;2-模擬量)uint8_t ModeCtrl;            // 模式修改方式(0-本地;1-遠程485;2-模擬量)uint8_t SetValCtrl;          // 設定修改方式(0-本地;1-遠程485;2-模擬量)uint8_t MasterSlaver;        // 主副機設置(0-主機;1-副機;2-單機)uint8_t TestMode;            // 測試模式uint8_t DebugMode;           // 調試模式uint8_t DeviceModel;         // 設備型號(0-3KW;2-20KW風冷)uint8_t DeviceSer[32];       // 設備序列號uint8_t AlarmEnable;         // 告警使能(0-關閉;1-使能)uint8_t CommProto;           // 通信協議(0-Modbus;1-Profibus)uint16_t UdcLimit;           // Udc調節限定值uint16_t IdcLimit;           // Idc調節限定值uint16_t PdcLimit;           // Pdc調節限定值uint8_t ModeSlect;           // 調節模式選擇(0-Udc;1-Idc;2-Pdc)uint8_t PWM1Freq;            // PWM1頻率(40~80表示40KHz~80KHz)
} DeviceParams;

5.2 存儲參數到 SPI FLASH

我們可以將參數結構體存儲到 SPI FLASH 的指定地址。以下是存儲函數的實現:

#include "stm32f1xx_hal.h"
#include "spi_flash.h"  // 假設這是 QW128 SPI FLASH 的驅動頭文件#define PARAMS_FLASH_ADDR 0x00000000  // 參數存儲的起始地址void SaveParamsToFlash(DeviceParams *params)
{// 擦除 SPI FLASH 的指定扇區QW128_SectorErase(PARAMS_FLASH_ADDR);// 將參數結構體寫入 SPI FLASHQW128_PageProgram(PARAMS_FLASH_ADDR, (uint8_t *)params, sizeof(DeviceParams));
}

5.3 從 SPI FLASH 讀取參數

從 SPI FLASH 中讀取參數結構體的實現如下:

void LoadParamsFromFlash(DeviceParams *params)
{// 從 SPI FLASH 讀取參數結構體QW128_ReadData(PARAMS_FLASH_ADDR, (uint8_t *)params, sizeof(DeviceParams));
}

5.4 示例:存儲和讀取參數

以下是一個完整的示例,展示如何初始化參數、存儲到 SPI FLASH 以及從 SPI FLASH 讀取參數:

int main(void)
{HAL_Init();SystemClock_Config();MX_SPI1_Init();  // 初始化 SPIMX_GPIO_Init();  // 初始化 GPIO// 初始化參數結構體DeviceParams params = {.CommBaud = BAUD_115200,.OnOffCtrl = 1,.ModeCtrl = 1,.SetValCtrl = 1,.MasterSlaver = 0,.TestMode = 0,.DebugMode = 1,.DeviceModel = 2,.DeviceSer = "1234567890ABCDEF1234567890ABCDEF",.AlarmEnable = 1,.CommProto = 0,.UdcLimit = 1000,.IdcLimit = 500,.PdcLimit = 2000,.ModeSlect = 1,.PWM1Freq = 60};// 存儲參數到 SPI FLASHSaveParamsToFlash(&params);// 從 SPI FLASH 讀取參數DeviceParams loadedParams;LoadParamsFromFlash(&loadedParams);// 驗證讀取的參數是否正確if (memcmp(&params, &loadedParams, sizeof(DeviceParams)) == 0){printf("Parameters loaded successfully!\n");}else{printf("Parameter load failed!\n");}while (1){// 主循環}
}

5.6 注意事項

  1. SPI FLASH 的壽命

    • SPI FLASH 的擦寫次數有限(通常為 10 萬次左右),頻繁擦寫可能導致損壞。建議在設計中盡量減少擦寫操作。
  2. 數據對齊

    • 確保參數結構體的數據對齊與 SPI FLASH 的頁大小(通常為 256 字節)匹配,避免跨頁寫入。
  3. 數據校驗

    • 在存儲和讀取參數時,可以添加 CRC 校驗或校驗和,確保數據的完整性。
  4. 備份機制

    • 可以使用乒乓存儲策略,將參數存儲在兩個不同的扇區中,確保在一個扇區損壞時可以從另一個扇區恢復數據。

6. 總結

本文介紹了如何在 STM32F103 上使用 SPI 接口操作 QW128 SPI FLASH,并通過乒乓存儲的方式實現數據備份。通過這種方式,可以有效地提高數據的可靠性和系統的穩定性。希望本文對大家有所幫助,歡迎在評論區留言討論。


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

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

相關文章

Leetcode2080:區間內查詢數字的頻率

題目描述&#xff1a; 請你設計一個數據結構&#xff0c;它能求出給定子數組內一個給定值的 頻率 。 子數組中一個值的 頻率 指的是這個子數組中這個值的出現次數。 請你實現 RangeFreqQuery 類&#xff1a; RangeFreqQuery(int[] arr) 用下標從 0 開始的整數數組 arr 構造…

Spring Boot自動裝配:約定大于配置的魔法解密

#### 一、自動裝配的哲學思考 在傳統Spring應用中&#xff0c;開發者需要手動配置大量的XML或JavaConfig。Spring Boot通過自動裝配機制實現了**約定大于配置**的設計理念&#xff0c;其核心思想可以概括為&#xff1a; 1. **智能預設**&#xff1a;基于類路徑檢測自動配置 2…

Fiddler筆記

文章目錄 一、與F12對比二、核心作用三、原理四、配置1.Rules:2.配置證書抓取https包3.設置過濾器4、抓取App包 五、模擬弱網測試六、調試1.線上調試2.斷點調試 七、理論1.四要素2.如何定位前后端bug 注 一、與F12對比 相同點&#xff1a; 都可以對http和https請求進行抓包分析…

Python爬蟲-貓眼電影的影院數據

前言 本文是該專欄的第46篇,后面會持續分享python爬蟲干貨知識,記得關注。 本文筆者以貓眼電影為例子,獲取貓眼的影院相關數據。 廢話不多說,具體實現思路和詳細邏輯,筆者將在正文結合完整代碼進行詳細介紹。接下來,跟著筆者直接往下看正文詳細內容。(附帶完整代碼) …

linux筆記:shell中的while、if、for語句

在Udig軟件的啟動腳本中使用了while循環、if語句、for循環&#xff0c;其他內容基本都是變量的定義&#xff0c;所以嘗試弄懂腳本中這三部分內容&#xff0c;了解腳本執行過程。 &#xff08;1&#xff09;while循環 while do循環內容如下所示&#xff0c;在循環中還用了expr…

利用分治策略優化快速排序

1. 基本思想 分治快速排序&#xff08;Quick Sort&#xff09;是一種基于分治法的排序算法&#xff0c;采用遞歸的方式將一個數組分割成小的子數組&#xff0c;并通過交換元素來使得每個子數組元素按照特定順序排列&#xff0c;最終將整個數組排序。 快速排序的基本步驟&#…

從零到一實現微信小程序計劃時鐘:完整教程

在本教程中&#xff0c;我們將一起實現一個微信小程序——計劃時鐘。這個小程序的核心功能是幫助用戶添加任務、設置任務的時間范圍&#xff0c;并且能夠刪除和查看已添加的任務。通過以下步驟&#xff0c;我們將帶你從零開始實現一個具有基本功能的微信小程序計劃時鐘。 項目…

idea日常報錯之UTF-8不可映射的字符

目錄 一、UTF-8不可映射的字符的解決 1、出現這種報錯的情形 2、具體解決辦法 前言&#xff1a; 在我們日常代碼編寫的時候可能會遇到各式各樣的錯誤&#xff0c;有時候并不是你改動了代碼&#xff0c;而是莫名其妙就出現的報錯&#xff0c;今天我就遇到一個在maven編譯的時候…

人工智能技術-基于長短期記憶(LSTM)網絡在交通流量預測中的應用

人工智能技術-基于長短期記憶&#xff08;LSTM&#xff09;網絡在交通流量預測中的應用 基于人工智能的智能交通管理系統 隨著城市化進程的加快&#xff0c;交通問題日益嚴峻。為了解決交通擁堵、減少交通事故、提高交通管理效率&#xff0c;人工智能&#xff08;AI&#xff…

HTTP FTP SMTP TELNET 應用協議

1. 標準和非標準的應用協議 標準應用協議&#xff1a; 由標準化組織&#xff08;如 IETF&#xff0c;Internet Engineering Task Force&#xff09;制定和維護&#xff0c;具有廣泛的通用性和互操作性。這些協議遵循嚴格的規范和標準&#xff0c;不同的實現之間可以很好地進行…

Matlab離線安裝硬件支持包的方法

想安裝支持樹莓派的包&#xff0c;但是發現通過matlab安裝需要續訂維護服務 可以通過離線的方式安裝。 1. 下載SupportSoftwareDownloader Support Software Downloader - MATLAB & Simulink 登錄賬號 選擇對應的版本 2. 選擇要安裝的包 3.將下載的包copy到安裝目錄下 …

Django REST Framework (DRF) 中用于構建 API 視圖類解析

Django REST Framework (DRF) 提供了豐富的視圖類&#xff0c;用于構建 API 視圖。這些視圖類可以分為以下幾類&#xff1a; 1. 基礎視圖類 這些是 DRF 中最基礎的視圖類&#xff0c;通常用于實現自定義邏輯。 常用類 APIView&#xff1a; 最基本的視圖類&#xff0c;所有其…

MyBatis攔截器終極指南:從原理到企業級實戰

在本篇文章中&#xff0c;我們將深入了解如何編寫一個 MyBatis 攔截器&#xff0c;并通過一個示例來展示如何在執行數據庫操作&#xff08;如插入或更新&#xff09;時&#xff0c;自動填充某些字段&#xff08;例如 createdBy 和 updatedBy&#xff09;信息。本文將詳細講解攔…

137,【4】 buuctf web [SCTF2019]Flag Shop

進入靶場 都點擊看看 發現點擊work會增加&#xffe5; 但肯定不能一直點下去 抓包看看 這看起來是一個 JWT&#xff08;JSON Web Token&#xff09;字符串。JWT 通常由三部分組成&#xff0c;通過點&#xff08;.&#xff09;分隔&#xff0c;分別是頭部&#xff08;Header&…

twisted實現MMORPG 游戲數據庫操作封裝設計與實現

在設計 MMORPG&#xff08;大規模多人在線角色扮演游戲&#xff09;時&#xff0c;數據庫系統是游戲架構中至關重要的一部分。數據庫不僅承擔了游戲中各種數據&#xff08;如玩家數據、物品數據、游戲世界狀態等&#xff09;的存儲和管理任務&#xff0c;還必須高效地支持并發訪…

【R語言】聚類分析

聚類分析是一種常用的無監督學習方法&#xff0c;是將所觀測的事物或者指標進行分類的一種統計分析方法&#xff0c;其目的是通過辨認在某些特征上相似的事物&#xff0c;并將它們分成各種類別。R語言提供了多種聚類分析的方法和包。 方法優點缺點適用場景K-means計算效率高需…

超全Deepseek資料包,deepseek下載安裝部署提示詞及本地部署指南介紹

該資料包涵蓋了DeepSeek模型的下載、安裝、部署以及本地運行的詳細指南&#xff0c;適合希望在本地環境中高效運行DeepSeek模型的用戶。資料包不僅包括基礎的安裝步驟&#xff0c;還提供了68G多套獨立部署視頻教程教程&#xff0c;針對不同硬件配置的模型選擇建議&#xff0c;以…

Java Spring boot 篇:常用注解

Configuration 作用 Configuration 注解的核心作用是把一個類標記為 Spring 應用上下文里的配置類。配置類就像一個 Java 版的 XML 配置文件&#xff0c;能夠在其中定義 Bean 定義和 Bean 之間的依賴關系。當 Spring 容器啟動時&#xff0c;會掃描這些配置類&#xff0c;解析其…

在 Ubuntu 20.04 為 Clash Verge AppImage 創建桌面圖標教程

在 Ubuntu 20.04 為 AppImage 創建桌面圖標教程 一、準備工作 確保你已經下載了 xxxx.AppImage 文件&#xff0c;并且知道它所在的具體路徑。同時&#xff0c;你可以準備一個合適的圖標文件&#xff08;.png 格式&#xff09;用于代表該應用程序&#xff0c;如果沒有合適的圖…

【復現DeepSeek-R1之Open R1實戰】系列6:GRPO源碼逐行深度解析(上)

目錄 4 GRPO源碼分析4.1 數據類 GRPOScriptArguments4.2 系統提示字符串 SYSTEM_PROMPT4.3 獎勵函數4.3.1 accuracy_reward函數4.3.2 verify函數4.3.3 format_reward函數 4.4 將數據集格式化為對話形式4.5 初始化GRPO Trainer 【復現DeepSeek-R1之Open R1實戰】系列3&#xff1…